source: mainline/uspace/app/websrv/websrv.c@ 08e103d4

Last change on this file since 08e103d4 was 08e103d4, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 7 years ago

Use clearer naming for string length functions

This and the following commit change the names of functions, as well as
their documentation, to use unambiguous terms "bytes" and "code points"
instead of ambiguous terms "size", "length", and "characters".

  • Property mode set to 100644
File size: 9.7 KB
Line 
1/*
2 * Copyright (c) 2012 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup websrv
30 * @{
31 */
32/**
33 * @file Skeletal web server.
34 */
35
36#include <errno.h>
37#include <assert.h>
38#include <stdbool.h>
39#include <stdio.h>
40#include <stdint.h>
41#include <stddef.h>
42#include <stdlib.h>
43#include <task.h>
44
45#include <vfs/vfs.h>
46
47#include <inet/addr.h>
48#include <inet/endpoint.h>
49#include <inet/tcp.h>
50
51#include <arg_parse.h>
52#include <macros.h>
53#include <str.h>
54#include <str_error.h>
55
56#define NAME "websrv"
57
58#define DEFAULT_PORT 8080
59
60#define WEB_ROOT "/data/web"
61
62/** Buffer for receiving the request. */
63#define BUFFER_SIZE 1024
64
65static void websrv_new_conn(tcp_listener_t *, tcp_conn_t *);
66
67static tcp_listen_cb_t listen_cb = {
68 .new_conn = websrv_new_conn
69};
70
71static tcp_cb_t conn_cb = {
72 .connected = NULL
73};
74
75static uint16_t port = DEFAULT_PORT;
76
77typedef struct {
78 tcp_conn_t *conn;
79
80 char rbuf[BUFFER_SIZE];
81 size_t rbuf_out;
82 size_t rbuf_in;
83
84 char lbuf[BUFFER_SIZE + 1];
85 size_t lbuf_used;
86} recv_t;
87
88static bool verbose = false;
89
90/** Responses to send to client. */
91
92static const char *msg_ok =
93 "HTTP/1.0 200 OK\r\n"
94 "\r\n";
95
96static const char *msg_bad_request =
97 "HTTP/1.0 400 Bad Request\r\n"
98 "\r\n"
99 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
100 "<html><head>\r\n"
101 "<title>400 Bad Request</title>\r\n"
102 "</head>\r\n"
103 "<body>\r\n"
104 "<h1>Bad Request</h1>\r\n"
105 "<p>The requested URL has bad syntax.</p>\r\n"
106 "</body>\r\n"
107 "</html>\r\n";
108
109static const char *msg_not_found =
110 "HTTP/1.0 404 Not Found\r\n"
111 "\r\n"
112 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
113 "<html><head>\r\n"
114 "<title>404 Not Found</title>\r\n"
115 "</head>\r\n"
116 "<body>\r\n"
117 "<h1>Not Found</h1>\r\n"
118 "<p>The requested URL was not found on this server.</p>\r\n"
119 "</body>\r\n"
120 "</html>\r\n";
121
122static const char *msg_not_implemented =
123 "HTTP/1.0 501 Not Implemented\r\n"
124 "\r\n"
125 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
126 "<html><head>\r\n"
127 "<title>501 Not Implemented</title>\r\n"
128 "</head>\r\n"
129 "<body>\r\n"
130 "<h1>Not Implemented</h1>\r\n"
131 "<p>The requested method is not implemented on this server.</p>\r\n"
132 "</body>\r\n"
133 "</html>\r\n";
134
135static errno_t recv_create(tcp_conn_t *conn, recv_t **rrecv)
136{
137 recv_t *recv;
138
139 recv = calloc(1, sizeof(recv_t));
140 if (recv == NULL)
141 return ENOMEM;
142
143 recv->conn = conn;
144 recv->rbuf_out = 0;
145 recv->rbuf_in = 0;
146 recv->lbuf_used = 0;
147
148 *rrecv = recv;
149 return EOK;
150}
151
152static void recv_destroy(recv_t *recv)
153{
154 free(recv);
155}
156
157/** Receive one character (with buffering) */
158static errno_t recv_char(recv_t *recv, char *c)
159{
160 size_t nrecv;
161 errno_t rc;
162
163 if (recv->rbuf_out == recv->rbuf_in) {
164 recv->rbuf_out = 0;
165 recv->rbuf_in = 0;
166
167 rc = tcp_conn_recv_wait(recv->conn, recv->rbuf, BUFFER_SIZE, &nrecv);
168 if (rc != EOK) {
169 fprintf(stderr, "tcp_conn_recv() failed: %s\n", str_error(rc));
170 return rc;
171 }
172
173 recv->rbuf_in = nrecv;
174 }
175
176 *c = recv->rbuf[recv->rbuf_out++];
177 return EOK;
178}
179
180/** Receive one line with length limit */
181static errno_t recv_line(recv_t *recv, char **rbuf)
182{
183 char *bp = recv->lbuf;
184 char c = '\0';
185
186 while (bp < recv->lbuf + BUFFER_SIZE) {
187 char prev = c;
188 errno_t rc = recv_char(recv, &c);
189
190 if (rc != EOK)
191 return rc;
192
193 *bp++ = c;
194 if ((prev == '\r') && (c == '\n'))
195 break;
196 }
197
198 recv->lbuf_used = bp - recv->lbuf;
199 *bp = '\0';
200
201 if (bp == recv->lbuf + BUFFER_SIZE)
202 return ELIMIT;
203
204 *rbuf = recv->lbuf;
205 return EOK;
206}
207
208static bool uri_is_valid(char *uri)
209{
210 if (uri[0] != '/')
211 return false;
212
213 if (uri[1] == '.')
214 return false;
215
216 char *cp = uri + 1;
217
218 while (*cp != '\0') {
219 char c = *cp++;
220 if (c == '/')
221 return false;
222 }
223
224 return true;
225}
226
227static errno_t send_response(tcp_conn_t *conn, const char *msg)
228{
229 size_t response_size = str_bytes(msg);
230
231 if (verbose)
232 fprintf(stderr, "Sending response\n");
233
234 errno_t rc = tcp_conn_send(conn, (void *) msg, response_size);
235 if (rc != EOK) {
236 fprintf(stderr, "tcp_conn_send() failed\n");
237 return rc;
238 }
239
240 return EOK;
241}
242
243static errno_t uri_get(const char *uri, tcp_conn_t *conn)
244{
245 char *fbuf = NULL;
246 char *fname = NULL;
247 errno_t rc;
248 size_t nr;
249 int fd = -1;
250
251 fbuf = calloc(BUFFER_SIZE, 1);
252 if (fbuf == NULL) {
253 rc = ENOMEM;
254 goto out;
255 }
256
257 if (str_cmp(uri, "/") == 0)
258 uri = "/index.html";
259
260 if (asprintf(&fname, "%s%s", WEB_ROOT, uri) < 0) {
261 rc = ENOMEM;
262 goto out;
263 }
264
265 rc = vfs_lookup_open(fname, WALK_REGULAR, MODE_READ, &fd);
266 if (rc != EOK) {
267 rc = send_response(conn, msg_not_found);
268 goto out;
269 }
270
271 free(fname);
272 fname = NULL;
273
274 rc = send_response(conn, msg_ok);
275 if (rc != EOK)
276 goto out;
277
278 aoff64_t pos = 0;
279 while (true) {
280 rc = vfs_read(fd, &pos, fbuf, BUFFER_SIZE, &nr);
281 if (rc != EOK)
282 goto out;
283
284 if (nr == 0)
285 break;
286
287 rc = tcp_conn_send(conn, fbuf, nr);
288 if (rc != EOK) {
289 fprintf(stderr, "tcp_conn_send() failed\n");
290 goto out;
291 }
292 }
293
294 rc = EOK;
295out:
296 if (fd >= 0)
297 vfs_put(fd);
298 free(fname);
299 free(fbuf);
300 return rc;
301}
302
303static errno_t req_process(tcp_conn_t *conn, recv_t *recv)
304{
305 char *reqline = NULL;
306
307 errno_t rc = recv_line(recv, &reqline);
308 if (rc != EOK) {
309 fprintf(stderr, "recv_line() failed\n");
310 return rc;
311 }
312
313 if (verbose)
314 fprintf(stderr, "Request: %s", reqline);
315
316 if (str_lcmp(reqline, "GET ", 4) != 0) {
317 rc = send_response(conn, msg_not_implemented);
318 return rc;
319 }
320
321 char *uri = reqline + 4;
322 char *end_uri = str_chr(uri, ' ');
323 if (end_uri == NULL) {
324 end_uri = reqline + str_bytes(reqline) - 2;
325 assert(*end_uri == '\r');
326 }
327
328 *end_uri = '\0';
329 if (verbose)
330 fprintf(stderr, "Requested URI: %s\n", uri);
331
332 if (!uri_is_valid(uri)) {
333 rc = send_response(conn, msg_bad_request);
334 return rc;
335 }
336
337 return uri_get(uri, conn);
338}
339
340static void usage(void)
341{
342 printf("Simple web server\n"
343 "\n"
344 "Usage: " NAME " [options]\n"
345 "\n"
346 "Where options are:\n"
347 "-p port_number | --port=port_number\n"
348 "\tListening port (default " STRING(DEFAULT_PORT) ").\n"
349 "\n"
350 "-h | --help\n"
351 "\tShow this application help.\n"
352 "-v | --verbose\n"
353 "\tVerbose mode\n");
354}
355
356static errno_t parse_option(int argc, char *argv[], int *index)
357{
358 int value;
359 errno_t rc;
360
361 switch (argv[*index][1]) {
362 case 'h':
363 usage();
364 exit(0);
365 break;
366 case 'p':
367 rc = arg_parse_int(argc, argv, index, &value, 0);
368 if (rc != EOK)
369 return rc;
370
371 port = (uint16_t) value;
372 break;
373 case 'v':
374 verbose = true;
375 break;
376 case '-':
377 /* Long options with double dash */
378 if (str_lcmp(argv[*index] + 2, "help", 5) == 0) {
379 usage();
380 exit(0);
381 } else if (str_lcmp(argv[*index] + 2, "port=", 5) == 0) {
382 rc = arg_parse_int(argc, argv, index, &value, 7);
383 if (rc != EOK)
384 return rc;
385
386 port = (uint16_t) value;
387 } else if (str_cmp(argv[*index] + 2, "verbose") == 0) {
388 verbose = true;
389 } else {
390 usage();
391 return EINVAL;
392 }
393 break;
394 default:
395 usage();
396 return EINVAL;
397 }
398
399 return EOK;
400}
401
402static void websrv_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
403{
404 errno_t rc;
405 recv_t *recv = NULL;
406
407 if (verbose)
408 fprintf(stderr, "New connection, waiting for request\n");
409
410 rc = recv_create(conn, &recv);
411 if (rc != EOK) {
412 fprintf(stderr, "Out of memory.\n");
413 goto error;
414 }
415
416 rc = req_process(conn, recv);
417 if (rc != EOK) {
418 fprintf(stderr, "Error processing request (%s)\n",
419 str_error(rc));
420 goto error;
421 }
422
423 rc = tcp_conn_send_fin(conn);
424 if (rc != EOK) {
425 fprintf(stderr, "Error sending FIN.\n");
426 goto error;
427 }
428
429 recv_destroy(recv);
430 return;
431error:
432 rc = tcp_conn_reset(conn);
433 if (rc != EOK)
434 fprintf(stderr, "Error resetting connection.\n");
435
436 recv_destroy(recv);
437}
438
439int main(int argc, char *argv[])
440{
441 inet_ep_t ep;
442 tcp_listener_t *lst;
443 tcp_t *tcp;
444 errno_t rc;
445
446 /* Parse command line arguments */
447 for (int i = 1; i < argc; i++) {
448 if (argv[i][0] == '-') {
449 rc = parse_option(argc, argv, &i);
450 if (rc != EOK)
451 return rc;
452 } else {
453 usage();
454 return EINVAL;
455 }
456 }
457
458 printf("%s: HelenOS web server\n", NAME);
459
460 if (verbose)
461 fprintf(stderr, "Creating listener\n");
462
463 inet_ep_init(&ep);
464 ep.port = port;
465
466 rc = tcp_create(&tcp);
467 if (rc != EOK) {
468 fprintf(stderr, "Error initializing TCP.\n");
469 return 1;
470 }
471
472 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
473 &lst);
474 if (rc != EOK) {
475 fprintf(stderr, "Error creating listener.\n");
476 return 2;
477 }
478
479 fprintf(stderr, "%s: Listening for connections at port %" PRIu16 "\n",
480 NAME, port);
481
482 task_retval(0);
483 async_manager();
484
485 /* Not reached */
486 return 0;
487}
488
489/** @}
490 */
Note: See TracBrowser for help on using the repository browser.