source: mainline/uspace/app/websrv/websrv.c@ 013e5d32

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 013e5d32 was a35b458, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 8 years ago

style: Remove trailing whitespace on _all_ lines, including empty ones, for particular file types.

Command used: tools/srepl '\s\+$' '' -- *.c *.h *.py *.sh *.s *.S *.ag

Currently, whitespace on empty lines is very inconsistent.
There are two basic choices: Either remove the whitespace, or keep empty lines
indented to the level of surrounding code. The former is AFAICT more common,
and also much easier to do automatically.

Alternatively, we could write script for automatic indentation, and use that
instead. However, if such a script exists, it's possible to use the indented
style locally, by having the editor apply relevant conversions on load/save,
without affecting remote repository. IMO, it makes more sense to adopt
the simpler rule.

  • 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
135
136static errno_t recv_create(tcp_conn_t *conn, recv_t **rrecv)
137{
138 recv_t *recv;
139
140 recv = calloc(1, sizeof(recv_t));
141 if (recv == NULL)
142 return ENOMEM;
143
144 recv->conn = conn;
145 recv->rbuf_out = 0;
146 recv->rbuf_in = 0;
147 recv->lbuf_used = 0;
148
149 *rrecv = recv;
150 return EOK;
151}
152
153static void recv_destroy(recv_t *recv)
154{
155 free(recv);
156}
157
158/** Receive one character (with buffering) */
159static errno_t recv_char(recv_t *recv, char *c)
160{
161 size_t nrecv;
162 errno_t rc;
163
164 if (recv->rbuf_out == recv->rbuf_in) {
165 recv->rbuf_out = 0;
166 recv->rbuf_in = 0;
167
168 rc = tcp_conn_recv_wait(recv->conn, recv->rbuf, BUFFER_SIZE, &nrecv);
169 if (rc != EOK) {
170 fprintf(stderr, "tcp_conn_recv() failed: %s\n", str_error(rc));
171 return rc;
172 }
173
174 recv->rbuf_in = nrecv;
175 }
176
177 *c = recv->rbuf[recv->rbuf_out++];
178 return EOK;
179}
180
181/** Receive one line with length limit */
182static errno_t recv_line(recv_t *recv, char **rbuf)
183{
184 char *bp = recv->lbuf;
185 char c = '\0';
186
187 while (bp < recv->lbuf + BUFFER_SIZE) {
188 char prev = c;
189 errno_t rc = recv_char(recv, &c);
190
191 if (rc != EOK)
192 return rc;
193
194 *bp++ = c;
195 if ((prev == '\r') && (c == '\n'))
196 break;
197 }
198
199 recv->lbuf_used = bp - recv->lbuf;
200 *bp = '\0';
201
202 if (bp == recv->lbuf + BUFFER_SIZE)
203 return ELIMIT;
204
205 *rbuf = recv->lbuf;
206 return EOK;
207}
208
209static bool uri_is_valid(char *uri)
210{
211 if (uri[0] != '/')
212 return false;
213
214 if (uri[1] == '.')
215 return false;
216
217 char *cp = uri + 1;
218
219 while (*cp != '\0') {
220 char c = *cp++;
221 if (c == '/')
222 return false;
223 }
224
225 return true;
226}
227
228static errno_t send_response(tcp_conn_t *conn, const char *msg)
229{
230 size_t response_size = str_size(msg);
231
232 if (verbose)
233 fprintf(stderr, "Sending response\n");
234
235 errno_t rc = tcp_conn_send(conn, (void *) msg, response_size);
236 if (rc != EOK) {
237 fprintf(stderr, "tcp_conn_send() failed\n");
238 return rc;
239 }
240
241 return EOK;
242}
243
244static errno_t uri_get(const char *uri, tcp_conn_t *conn)
245{
246 char *fbuf = NULL;
247 char *fname = NULL;
248 errno_t rc;
249 size_t nr;
250 int fd = -1;
251
252 fbuf = calloc(BUFFER_SIZE, 1);
253 if (fbuf == NULL) {
254 rc = ENOMEM;
255 goto out;
256 }
257
258 if (str_cmp(uri, "/") == 0)
259 uri = "/index.html";
260
261 if (asprintf(&fname, "%s%s", WEB_ROOT, uri) < 0) {
262 rc = ENOMEM;
263 goto out;
264 }
265
266 rc = vfs_lookup_open(fname, WALK_REGULAR, MODE_READ, &fd);
267 if (rc != EOK) {
268 rc = send_response(conn, msg_not_found);
269 goto out;
270 }
271
272 free(fname);
273 fname = NULL;
274
275 rc = send_response(conn, msg_ok);
276 if (rc != EOK)
277 goto out;
278
279 aoff64_t pos = 0;
280 while (true) {
281 rc = vfs_read(fd, &pos, fbuf, BUFFER_SIZE, &nr);
282 if (rc != EOK)
283 goto out;
284
285 if (nr == 0)
286 break;
287
288 rc = tcp_conn_send(conn, fbuf, nr);
289 if (rc != EOK) {
290 fprintf(stderr, "tcp_conn_send() failed\n");
291 goto out;
292 }
293 }
294
295 rc = EOK;
296out:
297 if (fd >= 0)
298 vfs_put(fd);
299 free(fname);
300 free(fbuf);
301 return rc;
302}
303
304static errno_t req_process(tcp_conn_t *conn, recv_t *recv)
305{
306 char *reqline = NULL;
307
308 errno_t rc = recv_line(recv, &reqline);
309 if (rc != EOK) {
310 fprintf(stderr, "recv_line() failed\n");
311 return rc;
312 }
313
314 if (verbose)
315 fprintf(stderr, "Request: %s", reqline);
316
317 if (str_lcmp(reqline, "GET ", 4) != 0) {
318 rc = send_response(conn, msg_not_implemented);
319 return rc;
320 }
321
322 char *uri = reqline + 4;
323 char *end_uri = str_chr(uri, ' ');
324 if (end_uri == NULL) {
325 end_uri = reqline + str_size(reqline) - 2;
326 assert(*end_uri == '\r');
327 }
328
329 *end_uri = '\0';
330 if (verbose)
331 fprintf(stderr, "Requested URI: %s\n", uri);
332
333 if (!uri_is_valid(uri)) {
334 rc = send_response(conn, msg_bad_request);
335 return rc;
336 }
337
338 return uri_get(uri, conn);
339}
340
341static void usage(void)
342{
343 printf("Simple web server\n"
344 "\n"
345 "Usage: " NAME " [options]\n"
346 "\n"
347 "Where options are:\n"
348 "-p port_number | --port=port_number\n"
349 "\tListening port (default " STRING(DEFAULT_PORT) ").\n"
350 "\n"
351 "-h | --help\n"
352 "\tShow this application help.\n"
353 "-v | --verbose\n"
354 "\tVerbose mode\n");
355}
356
357static errno_t parse_option(int argc, char *argv[], int *index)
358{
359 int value;
360 errno_t rc;
361
362 switch (argv[*index][1]) {
363 case 'h':
364 usage();
365 exit(0);
366 break;
367 case 'p':
368 rc = arg_parse_int(argc, argv, index, &value, 0);
369 if (rc != EOK)
370 return rc;
371
372 port = (uint16_t) value;
373 break;
374 case 'v':
375 verbose = true;
376 break;
377 /* Long options with double dash */
378 case '-':
379 if (str_lcmp(argv[*index] + 2, "help", 5) == 0) {
380 usage();
381 exit(0);
382 } else if (str_lcmp(argv[*index] + 2, "port=", 5) == 0) {
383 rc = arg_parse_int(argc, argv, index, &value, 7);
384 if (rc != EOK)
385 return rc;
386
387 port = (uint16_t) value;
388 } else if (str_cmp(argv[*index] +2, "verbose") == 0) {
389 verbose = true;
390 } else {
391 usage();
392 return EINVAL;
393 }
394 break;
395 default:
396 usage();
397 return EINVAL;
398 }
399
400 return EOK;
401}
402
403static void websrv_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
404{
405 errno_t rc;
406 recv_t *recv = NULL;
407
408 if (verbose)
409 fprintf(stderr, "New connection, waiting for request\n");
410
411 rc = recv_create(conn, &recv);
412 if (rc != EOK) {
413 fprintf(stderr, "Out of memory.\n");
414 goto error;
415 }
416
417 rc = req_process(conn, recv);
418 if (rc != EOK) {
419 fprintf(stderr, "Error processing request (%s)\n",
420 str_error(rc));
421 goto error;
422 }
423
424 rc = tcp_conn_send_fin(conn);
425 if (rc != EOK) {
426 fprintf(stderr, "Error sending FIN.\n");
427 goto error;
428 }
429
430 recv_destroy(recv);
431 return;
432error:
433 rc = tcp_conn_reset(conn);
434 if (rc != EOK)
435 fprintf(stderr, "Error resetting connection.\n");
436
437 recv_destroy(recv);
438}
439
440int main(int argc, char *argv[])
441{
442 inet_ep_t ep;
443 tcp_listener_t *lst;
444 tcp_t *tcp;
445 errno_t rc;
446
447 /* Parse command line arguments */
448 for (int i = 1; i < argc; i++) {
449 if (argv[i][0] == '-') {
450 rc = parse_option(argc, argv, &i);
451 if (rc != EOK)
452 return rc;
453 } else {
454 usage();
455 return EINVAL;
456 }
457 }
458
459 printf("%s: HelenOS web server\n", NAME);
460
461 if (verbose)
462 fprintf(stderr, "Creating listener\n");
463
464 inet_ep_init(&ep);
465 ep.port = port;
466
467 rc = tcp_create(&tcp);
468 if (rc != EOK) {
469 fprintf(stderr, "Error initializing TCP.\n");
470 return 1;
471 }
472
473 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
474 &lst);
475 if (rc != EOK) {
476 fprintf(stderr, "Error creating listener.\n");
477 return 2;
478 }
479
480 fprintf(stderr, "%s: Listening for connections at port %" PRIu16 "\n",
481 NAME, port);
482
483 task_retval(0);
484 async_manager();
485
486 /* Not reached */
487 return 0;
488}
489
490/** @}
491 */
Note: See TracBrowser for help on using the repository browser.