source: mainline/uspace/app/websrv/websrv.c@ 8e3498b

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 8e3498b was 8e3498b, checked in by Jiri Svoboda <jiri@…>, 8 years ago

vfs_read/write() should return error code separately from number of bytes transferred.

  • 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 int 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 int recv_char(recv_t *recv, char *c)
160{
161 size_t nrecv;
162 int 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 (%d)\n", 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 int 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 int 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 int 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 int 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 int uri_get(const char *uri, tcp_conn_t *conn)
245{
246 char *fbuf = NULL;
247 char *fname = NULL;
248 int 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 rc = asprintf(&fname, "%s%s", WEB_ROOT, uri);
262 if (rc < 0) {
263 rc = ENOMEM;
264 goto out;
265 }
266
267 fd = vfs_lookup_open(fname, WALK_REGULAR, MODE_READ);
268 if (fd < 0) {
269 rc = send_response(conn, msg_not_found);
270 goto out;
271 }
272
273 free(fname);
274 fname = NULL;
275
276 rc = send_response(conn, msg_ok);
277 if (rc != EOK)
278 goto out;
279
280 aoff64_t pos = 0;
281 while (true) {
282 rc = vfs_read(fd, &pos, fbuf, BUFFER_SIZE, &nr);
283 if (rc != EOK)
284 goto out;
285
286 if (nr == 0)
287 break;
288
289 rc = tcp_conn_send(conn, fbuf, nr);
290 if (rc != EOK) {
291 fprintf(stderr, "tcp_conn_send() failed\n");
292 goto out;
293 }
294 }
295
296 rc = EOK;
297out:
298 if (fd >= 0)
299 vfs_put(fd);
300 free(fname);
301 free(fbuf);
302 return rc;
303}
304
305static int req_process(tcp_conn_t *conn, recv_t *recv)
306{
307 char *reqline = NULL;
308
309 int rc = recv_line(recv, &reqline);
310 if (rc != EOK) {
311 fprintf(stderr, "recv_line() failed\n");
312 return rc;
313 }
314
315 if (verbose)
316 fprintf(stderr, "Request: %s", reqline);
317
318 if (str_lcmp(reqline, "GET ", 4) != 0) {
319 rc = send_response(conn, msg_not_implemented);
320 return rc;
321 }
322
323 char *uri = reqline + 4;
324 char *end_uri = str_chr(uri, ' ');
325 if (end_uri == NULL) {
326 end_uri = reqline + str_size(reqline) - 2;
327 assert(*end_uri == '\r');
328 }
329
330 *end_uri = '\0';
331 if (verbose)
332 fprintf(stderr, "Requested URI: %s\n", uri);
333
334 if (!uri_is_valid(uri)) {
335 rc = send_response(conn, msg_bad_request);
336 return rc;
337 }
338
339 return uri_get(uri, conn);
340}
341
342static void usage(void)
343{
344 printf("Simple web server\n"
345 "\n"
346 "Usage: " NAME " [options]\n"
347 "\n"
348 "Where options are:\n"
349 "-p port_number | --port=port_number\n"
350 "\tListening port (default " STRING(DEFAULT_PORT) ").\n"
351 "\n"
352 "-h | --help\n"
353 "\tShow this application help.\n"
354 "-v | --verbose\n"
355 "\tVerbose mode\n");
356}
357
358static int parse_option(int argc, char *argv[], int *index)
359{
360 int value;
361 int rc;
362
363 switch (argv[*index][1]) {
364 case 'h':
365 usage();
366 exit(0);
367 break;
368 case 'p':
369 rc = arg_parse_int(argc, argv, index, &value, 0);
370 if (rc != EOK)
371 return rc;
372
373 port = (uint16_t) value;
374 break;
375 case 'v':
376 verbose = true;
377 break;
378 /* Long options with double dash */
379 case '-':
380 if (str_lcmp(argv[*index] + 2, "help", 5) == 0) {
381 usage();
382 exit(0);
383 } else if (str_lcmp(argv[*index] + 2, "port=", 5) == 0) {
384 rc = arg_parse_int(argc, argv, index, &value, 7);
385 if (rc != EOK)
386 return rc;
387
388 port = (uint16_t) value;
389 } else if (str_cmp(argv[*index] +2, "verbose") == 0) {
390 verbose = true;
391 } else {
392 usage();
393 return EINVAL;
394 }
395 break;
396 default:
397 usage();
398 return EINVAL;
399 }
400
401 return EOK;
402}
403
404static void websrv_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
405{
406 int rc;
407 recv_t *recv = NULL;
408
409 if (verbose)
410 fprintf(stderr, "New connection, waiting for request\n");
411
412 rc = recv_create(conn, &recv);
413 if (rc != EOK) {
414 fprintf(stderr, "Out of memory.\n");
415 goto error;
416 }
417
418 rc = req_process(conn, recv);
419 if (rc != EOK) {
420 fprintf(stderr, "Error processing request (%s)\n",
421 str_error(rc));
422 goto error;
423 }
424
425 rc = tcp_conn_send_fin(conn);
426 if (rc != EOK) {
427 fprintf(stderr, "Error sending FIN.\n");
428 goto error;
429 }
430
431 recv_destroy(recv);
432 return;
433error:
434 rc = tcp_conn_reset(conn);
435 if (rc != EOK)
436 fprintf(stderr, "Error resetting connection.\n");
437
438 recv_destroy(recv);
439}
440
441int main(int argc, char *argv[])
442{
443 inet_ep_t ep;
444 tcp_listener_t *lst;
445 tcp_t *tcp;
446 int rc;
447
448 /* Parse command line arguments */
449 for (int i = 1; i < argc; i++) {
450 if (argv[i][0] == '-') {
451 rc = parse_option(argc, argv, &i);
452 if (rc != EOK)
453 return rc;
454 } else {
455 usage();
456 return EINVAL;
457 }
458 }
459
460 printf("%s: HelenOS web server\n", NAME);
461
462 if (verbose)
463 fprintf(stderr, "Creating listener\n");
464
465 inet_ep_init(&ep);
466 ep.port = port;
467
468 rc = tcp_create(&tcp);
469 if (rc != EOK) {
470 fprintf(stderr, "Error initializing TCP.\n");
471 return 1;
472 }
473
474 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
475 &lst);
476 if (rc != EOK) {
477 fprintf(stderr, "Error creating listener.\n");
478 return 2;
479 }
480
481 fprintf(stderr, "%s: Listening for connections at port %" PRIu16 "\n",
482 NAME, port);
483
484 task_retval(0);
485 async_manager();
486
487 /* Not reached */
488 return 0;
489}
490
491/** @}
492 */
Note: See TracBrowser for help on using the repository browser.