source: mainline/uspace/app/websrv/websrv.c@ 5ef16903

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5ef16903 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
RevLine 
[f4a2d624]1/*
[26b6789]2 * Copyright (c) 2012 Jiri Svoboda
[f4a2d624]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/**
[4a4cc150]33 * @file Skeletal web server.
[f4a2d624]34 */
35
[4a4cc150]36#include <errno.h>
[d4d74dc]37#include <assert.h>
[8d2dd7f2]38#include <stdbool.h>
[f4a2d624]39#include <stdio.h>
[8d2dd7f2]40#include <stdint.h>
41#include <stddef.h>
[9c3bba0]42#include <stdlib.h>
[26b6789]43#include <task.h>
[f4a2d624]44
[b19e892]45#include <vfs/vfs.h>
46
[fab2746]47#include <inet/addr.h>
48#include <inet/endpoint.h>
49#include <inet/tcp.h>
[f4a2d624]50
[0a549cc]51#include <arg_parse.h>
52#include <macros.h>
[f4a2d624]53#include <str.h>
[0a549cc]54#include <str_error.h>
[f4a2d624]55
[0a549cc]56#define NAME "websrv"
[f4a2d624]57
[0a549cc]58#define DEFAULT_PORT 8080
59
60#define WEB_ROOT "/data/web"
[4a4cc150]61
[f4a2d624]62/** Buffer for receiving the request. */
[0a549cc]63#define BUFFER_SIZE 1024
64
[fab2746]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
[0a549cc]75static uint16_t port = DEFAULT_PORT;
76
[d076f16]77typedef struct {
78 tcp_conn_t *conn;
[4a4cc150]79
[d076f16]80 char rbuf[BUFFER_SIZE];
81 size_t rbuf_out;
82 size_t rbuf_in;
[4a4cc150]83
[d076f16]84 char lbuf[BUFFER_SIZE + 1];
85 size_t lbuf_used;
86} recv_t;
[f4a2d624]87
[26b6789]88static bool verbose = false;
89
[0a549cc]90/** Responses to send to client. */
91
92static const char *msg_ok =
[f4a2d624]93 "HTTP/1.0 200 OK\r\n"
[4a4cc150]94 "\r\n";
95
[0a549cc]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
[d076f16]135
[b7fd2a0]136static errno_t recv_create(tcp_conn_t *conn, recv_t **rrecv)
[d076f16]137{
138 recv_t *recv;
[a35b458]139
[d076f16]140 recv = calloc(1, sizeof(recv_t));
141 if (recv == NULL)
142 return ENOMEM;
[a35b458]143
[d076f16]144 recv->conn = conn;
145 recv->rbuf_out = 0;
146 recv->rbuf_in = 0;
147 recv->lbuf_used = 0;
[a35b458]148
[d076f16]149 *rrecv = recv;
150 return EOK;
151}
152
153static void recv_destroy(recv_t *recv)
154{
155 free(recv);
156}
157
[4a4cc150]158/** Receive one character (with buffering) */
[b7fd2a0]159static errno_t recv_char(recv_t *recv, char *c)
[4a4cc150]160{
[fab2746]161 size_t nrecv;
[b7fd2a0]162 errno_t rc;
[a35b458]163
[d076f16]164 if (recv->rbuf_out == recv->rbuf_in) {
165 recv->rbuf_out = 0;
166 recv->rbuf_in = 0;
[a35b458]167
[d076f16]168 rc = tcp_conn_recv_wait(recv->conn, recv->rbuf, BUFFER_SIZE, &nrecv);
[fab2746]169 if (rc != EOK) {
[c1694b6b]170 fprintf(stderr, "tcp_conn_recv() failed: %s\n", str_error(rc));
[4a4cc150]171 return rc;
172 }
[a35b458]173
[d076f16]174 recv->rbuf_in = nrecv;
[4a4cc150]175 }
[a35b458]176
[d076f16]177 *c = recv->rbuf[recv->rbuf_out++];
[4a4cc150]178 return EOK;
179}
180
[0a549cc]181/** Receive one line with length limit */
[b7fd2a0]182static errno_t recv_line(recv_t *recv, char **rbuf)
[4a4cc150]183{
[d076f16]184 char *bp = recv->lbuf;
[0a549cc]185 char c = '\0';
[a35b458]186
[d076f16]187 while (bp < recv->lbuf + BUFFER_SIZE) {
[0a549cc]188 char prev = c;
[b7fd2a0]189 errno_t rc = recv_char(recv, &c);
[a35b458]190
[4a4cc150]191 if (rc != EOK)
192 return rc;
[a35b458]193
[4a4cc150]194 *bp++ = c;
[0a549cc]195 if ((prev == '\r') && (c == '\n'))
[4a4cc150]196 break;
197 }
[a35b458]198
[d076f16]199 recv->lbuf_used = bp - recv->lbuf;
[4a4cc150]200 *bp = '\0';
[a35b458]201
[d076f16]202 if (bp == recv->lbuf + BUFFER_SIZE)
[4a4cc150]203 return ELIMIT;
[a35b458]204
[d076f16]205 *rbuf = recv->lbuf;
[4a4cc150]206 return EOK;
207}
208
209static bool uri_is_valid(char *uri)
210{
211 if (uri[0] != '/')
212 return false;
[a35b458]213
[4a4cc150]214 if (uri[1] == '.')
215 return false;
[a35b458]216
[0a549cc]217 char *cp = uri + 1;
[a35b458]218
[4a4cc150]219 while (*cp != '\0') {
[0a549cc]220 char c = *cp++;
[4a4cc150]221 if (c == '/')
222 return false;
223 }
[a35b458]224
[4a4cc150]225 return true;
226}
227
[b7fd2a0]228static errno_t send_response(tcp_conn_t *conn, const char *msg)
[4a4cc150]229{
[0a549cc]230 size_t response_size = str_size(msg);
[a35b458]231
[26b6789]232 if (verbose)
233 fprintf(stderr, "Sending response\n");
[a35b458]234
[b7fd2a0]235 errno_t rc = tcp_conn_send(conn, (void *) msg, response_size);
[fab2746]236 if (rc != EOK) {
237 fprintf(stderr, "tcp_conn_send() failed\n");
[4a4cc150]238 return rc;
239 }
[a35b458]240
[4a4cc150]241 return EOK;
242}
243
[b7fd2a0]244static errno_t uri_get(const char *uri, tcp_conn_t *conn)
[4a4cc150]245{
[d076f16]246 char *fbuf = NULL;
247 char *fname = NULL;
[b7fd2a0]248 errno_t rc;
[8e3498b]249 size_t nr;
[d076f16]250 int fd = -1;
[a35b458]251
[d076f16]252 fbuf = calloc(BUFFER_SIZE, 1);
253 if (fbuf == NULL) {
254 rc = ENOMEM;
255 goto out;
256 }
[a35b458]257
[4a4cc150]258 if (str_cmp(uri, "/") == 0)
[6bb169b5]259 uri = "/index.html";
[a35b458]260
[d5c1051]261 if (asprintf(&fname, "%s%s", WEB_ROOT, uri) < 0) {
[d076f16]262 rc = ENOMEM;
263 goto out;
264 }
[a35b458]265
[f77c1c9]266 rc = vfs_lookup_open(fname, WALK_REGULAR, MODE_READ, &fd);
267 if (rc != EOK) {
[fab2746]268 rc = send_response(conn, msg_not_found);
[d076f16]269 goto out;
[4a4cc150]270 }
[a35b458]271
[9c3bba0]272 free(fname);
[d076f16]273 fname = NULL;
[a35b458]274
[fab2746]275 rc = send_response(conn, msg_ok);
[4a4cc150]276 if (rc != EOK)
[d076f16]277 goto out;
[a35b458]278
[58898d1d]279 aoff64_t pos = 0;
[4a4cc150]280 while (true) {
[8e3498b]281 rc = vfs_read(fd, &pos, fbuf, BUFFER_SIZE, &nr);
282 if (rc != EOK)
283 goto out;
[a35b458]284
[4a4cc150]285 if (nr == 0)
286 break;
[a35b458]287
[fab2746]288 rc = tcp_conn_send(conn, fbuf, nr);
289 if (rc != EOK) {
290 fprintf(stderr, "tcp_conn_send() failed\n");
[d076f16]291 goto out;
[4a4cc150]292 }
293 }
[a35b458]294
[d076f16]295 rc = EOK;
296out:
297 if (fd >= 0)
298 vfs_put(fd);
299 free(fname);
300 free(fbuf);
301 return rc;
[4a4cc150]302}
303
[b7fd2a0]304static errno_t req_process(tcp_conn_t *conn, recv_t *recv)
[4a4cc150]305{
[d076f16]306 char *reqline = NULL;
307
[b7fd2a0]308 errno_t rc = recv_line(recv, &reqline);
[4a4cc150]309 if (rc != EOK) {
[0a549cc]310 fprintf(stderr, "recv_line() failed\n");
[4a4cc150]311 return rc;
312 }
[a35b458]313
[26b6789]314 if (verbose)
[d076f16]315 fprintf(stderr, "Request: %s", reqline);
[a35b458]316
[d076f16]317 if (str_lcmp(reqline, "GET ", 4) != 0) {
[fab2746]318 rc = send_response(conn, msg_not_implemented);
[0a549cc]319 return rc;
[4a4cc150]320 }
[a35b458]321
[d076f16]322 char *uri = reqline + 4;
[0a549cc]323 char *end_uri = str_chr(uri, ' ');
[4a4cc150]324 if (end_uri == NULL) {
[d076f16]325 end_uri = reqline + str_size(reqline) - 2;
[4a4cc150]326 assert(*end_uri == '\r');
327 }
[a35b458]328
[4a4cc150]329 *end_uri = '\0';
[26b6789]330 if (verbose)
331 fprintf(stderr, "Requested URI: %s\n", uri);
[a35b458]332
[4a4cc150]333 if (!uri_is_valid(uri)) {
[fab2746]334 rc = send_response(conn, msg_bad_request);
[0a549cc]335 return rc;
[4a4cc150]336 }
[a35b458]337
[fab2746]338 return uri_get(uri, conn);
[4a4cc150]339}
[f4a2d624]340
[0a549cc]341static void usage(void)
[f4a2d624]342{
[d076f16]343 printf("Simple web server\n"
[0a549cc]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"
[26b6789]352 "\tShow this application help.\n"
353 "-v | --verbose\n"
354 "\tVerbose mode\n");
[0a549cc]355}
[f4a2d624]356
[b7fd2a0]357static errno_t parse_option(int argc, char *argv[], int *index)
[0a549cc]358{
359 int value;
[b7fd2a0]360 errno_t rc;
[a35b458]361
[0a549cc]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;
[a35b458]371
[0a549cc]372 port = (uint16_t) value;
373 break;
[26b6789]374 case 'v':
375 verbose = true;
376 break;
[0a549cc]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;
[a35b458]386
[0a549cc]387 port = (uint16_t) value;
[26b6789]388 } else if (str_cmp(argv[*index] +2, "verbose") == 0) {
389 verbose = true;
[0a549cc]390 } else {
391 usage();
392 return EINVAL;
393 }
394 break;
395 default:
396 usage();
397 return EINVAL;
398 }
[a35b458]399
[0a549cc]400 return EOK;
401}
[f4a2d624]402
[fab2746]403static void websrv_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
404{
[b7fd2a0]405 errno_t rc;
[d076f16]406 recv_t *recv = NULL;
[a35b458]407
[fab2746]408 if (verbose)
409 fprintf(stderr, "New connection, waiting for request\n");
[a35b458]410
[d076f16]411 rc = recv_create(conn, &recv);
412 if (rc != EOK) {
413 fprintf(stderr, "Out of memory.\n");
414 goto error;
415 }
[a35b458]416
[d076f16]417 rc = req_process(conn, recv);
[fab2746]418 if (rc != EOK) {
419 fprintf(stderr, "Error processing request (%s)\n",
420 str_error(rc));
[d076f16]421 goto error;
[b99f6e2]422 }
[a35b458]423
[b99f6e2]424 rc = tcp_conn_send_fin(conn);
425 if (rc != EOK) {
426 fprintf(stderr, "Error sending FIN.\n");
[d076f16]427 goto error;
[fab2746]428 }
[d076f16]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");
[a35b458]436
[d076f16]437 recv_destroy(recv);
[fab2746]438}
439
[0a549cc]440int main(int argc, char *argv[])
441{
[fab2746]442 inet_ep_t ep;
443 tcp_listener_t *lst;
444 tcp_t *tcp;
[b7fd2a0]445 errno_t rc;
[a35b458]446
[0a549cc]447 /* Parse command line arguments */
448 for (int i = 1; i < argc; i++) {
449 if (argv[i][0] == '-') {
[fab2746]450 rc = parse_option(argc, argv, &i);
[0a549cc]451 if (rc != EOK)
452 return rc;
453 } else {
454 usage();
455 return EINVAL;
456 }
457 }
[a35b458]458
[26b6789]459 printf("%s: HelenOS web server\n", NAME);
460
461 if (verbose)
[fab2746]462 fprintf(stderr, "Creating listener\n");
[a35b458]463
[fab2746]464 inet_ep_init(&ep);
465 ep.port = port;
466
467 rc = tcp_create(&tcp);
[f4a2d624]468 if (rc != EOK) {
[fab2746]469 fprintf(stderr, "Error initializing TCP.\n");
470 return 1;
[f4a2d624]471 }
[fab2746]472
473 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
474 &lst);
[f4a2d624]475 if (rc != EOK) {
[fab2746]476 fprintf(stderr, "Error creating listener.\n");
477 return 2;
[f4a2d624]478 }
[a35b458]479
[26b6789]480 fprintf(stderr, "%s: Listening for connections at port %" PRIu16 "\n",
481 NAME, port);
482
483 task_retval(0);
[fab2746]484 async_manager();
[a35b458]485
[0a549cc]486 /* Not reached */
[f4a2d624]487 return 0;
488}
489
490/** @}
491 */
Note: See TracBrowser for help on using the repository browser.