source: mainline/uspace/app/websrv/websrv.c@ b99f6e2

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

Accepting connections.

  • Property mode set to 100644
File size: 8.8 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
[3e6a98c5]36#include <stdbool.h>
[4a4cc150]37#include <errno.h>
[d4d74dc]38#include <assert.h>
[f4a2d624]39#include <stdio.h>
[4a4cc150]40#include <sys/types.h>
41#include <sys/stat.h>
[9c3bba0]42#include <stdlib.h>
[4a4cc150]43#include <fcntl.h>
[26b6789]44#include <task.h>
[f4a2d624]45
[fab2746]46#include <inet/addr.h>
47#include <inet/endpoint.h>
48#include <inet/tcp.h>
[f4a2d624]49
[0a549cc]50#include <arg_parse.h>
51#include <macros.h>
[f4a2d624]52#include <str.h>
[0a549cc]53#include <str_error.h>
[f4a2d624]54
[0a549cc]55#define NAME "websrv"
[f4a2d624]56
[0a549cc]57#define DEFAULT_PORT 8080
58
59#define WEB_ROOT "/data/web"
[4a4cc150]60
[f4a2d624]61/** Buffer for receiving the request. */
[0a549cc]62#define BUFFER_SIZE 1024
63
[fab2746]64static void websrv_new_conn(tcp_listener_t *, tcp_conn_t *);
65
66static tcp_listen_cb_t listen_cb = {
67 .new_conn = websrv_new_conn
68};
69
70static tcp_cb_t conn_cb = {
71 .connected = NULL
72};
73
[0a549cc]74static uint16_t port = DEFAULT_PORT;
75
[4a4cc150]76static char rbuf[BUFFER_SIZE];
[0a549cc]77static size_t rbuf_out;
78static size_t rbuf_in;
[4a4cc150]79
80static char lbuf[BUFFER_SIZE + 1];
81static size_t lbuf_used;
82
83static char fbuf[BUFFER_SIZE];
[f4a2d624]84
[26b6789]85static bool verbose = false;
86
[0a549cc]87/** Responses to send to client. */
88
89static const char *msg_ok =
[f4a2d624]90 "HTTP/1.0 200 OK\r\n"
[4a4cc150]91 "\r\n";
92
[0a549cc]93static const char *msg_bad_request =
94 "HTTP/1.0 400 Bad Request\r\n"
95 "\r\n"
96 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
97 "<html><head>\r\n"
98 "<title>400 Bad Request</title>\r\n"
99 "</head>\r\n"
100 "<body>\r\n"
101 "<h1>Bad Request</h1>\r\n"
102 "<p>The requested URL has bad syntax.</p>\r\n"
103 "</body>\r\n"
104 "</html>\r\n";
105
106static const char *msg_not_found =
107 "HTTP/1.0 404 Not Found\r\n"
108 "\r\n"
109 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
110 "<html><head>\r\n"
111 "<title>404 Not Found</title>\r\n"
112 "</head>\r\n"
113 "<body>\r\n"
114 "<h1>Not Found</h1>\r\n"
115 "<p>The requested URL was not found on this server.</p>\r\n"
116 "</body>\r\n"
117 "</html>\r\n";
118
119static const char *msg_not_implemented =
120 "HTTP/1.0 501 Not Implemented\r\n"
121 "\r\n"
122 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
123 "<html><head>\r\n"
124 "<title>501 Not Implemented</title>\r\n"
125 "</head>\r\n"
126 "<body>\r\n"
127 "<h1>Not Implemented</h1>\r\n"
128 "<p>The requested method is not implemented on this server.</p>\r\n"
129 "</body>\r\n"
130 "</html>\r\n";
131
[4a4cc150]132/** Receive one character (with buffering) */
[fab2746]133static int recv_char(tcp_conn_t *conn, char *c)
[4a4cc150]134{
[fab2746]135 size_t nrecv;
136 int rc;
137
[4a4cc150]138 if (rbuf_out == rbuf_in) {
139 rbuf_out = 0;
140 rbuf_in = 0;
[0a549cc]141
[fab2746]142 rc = tcp_conn_recv_wait(conn, rbuf, BUFFER_SIZE, &nrecv);
143 if (rc != EOK) {
[b99f6e2]144 fprintf(stderr, "tcp_conn_recv() failed (%d)\n", rc);
[4a4cc150]145 return rc;
146 }
[0a549cc]147
[fab2746]148 rbuf_in = nrecv;
[4a4cc150]149 }
[0a549cc]150
[4a4cc150]151 *c = rbuf[rbuf_out++];
152 return EOK;
153}
154
[0a549cc]155/** Receive one line with length limit */
[fab2746]156static int recv_line(tcp_conn_t *conn)
[4a4cc150]157{
[0a549cc]158 char *bp = lbuf;
159 char c = '\0';
160
[4a4cc150]161 while (bp < lbuf + BUFFER_SIZE) {
[0a549cc]162 char prev = c;
[fab2746]163 int rc = recv_char(conn, &c);
[0a549cc]164
[4a4cc150]165 if (rc != EOK)
166 return rc;
[0a549cc]167
[4a4cc150]168 *bp++ = c;
[0a549cc]169 if ((prev == '\r') && (c == '\n'))
[4a4cc150]170 break;
171 }
[0a549cc]172
[4a4cc150]173 lbuf_used = bp - lbuf;
174 *bp = '\0';
[0a549cc]175
[4a4cc150]176 if (bp == lbuf + BUFFER_SIZE)
177 return ELIMIT;
[0a549cc]178
[4a4cc150]179 return EOK;
180}
181
182static bool uri_is_valid(char *uri)
183{
184 if (uri[0] != '/')
185 return false;
[0a549cc]186
[4a4cc150]187 if (uri[1] == '.')
188 return false;
[0a549cc]189
190 char *cp = uri + 1;
191
[4a4cc150]192 while (*cp != '\0') {
[0a549cc]193 char c = *cp++;
[4a4cc150]194 if (c == '/')
195 return false;
196 }
[0a549cc]197
[4a4cc150]198 return true;
199}
200
[fab2746]201static int send_response(tcp_conn_t *conn, const char *msg)
[4a4cc150]202{
[0a549cc]203 size_t response_size = str_size(msg);
204
[26b6789]205 if (verbose)
206 fprintf(stderr, "Sending response\n");
207
[fab2746]208 int rc = tcp_conn_send(conn, (void *) msg, response_size);
209 if (rc != EOK) {
210 fprintf(stderr, "tcp_conn_send() failed\n");
[4a4cc150]211 return rc;
212 }
[0a549cc]213
[4a4cc150]214 return EOK;
215}
216
[fab2746]217static int uri_get(const char *uri, tcp_conn_t *conn)
[4a4cc150]218{
219 if (str_cmp(uri, "/") == 0)
[6bb169b5]220 uri = "/index.html";
[0a549cc]221
222 char *fname;
223 int rc = asprintf(&fname, "%s%s", WEB_ROOT, uri);
[4a4cc150]224 if (rc < 0)
225 return ENOMEM;
[0a549cc]226
227 int fd = open(fname, O_RDONLY);
[4a4cc150]228 if (fd < 0) {
[fab2746]229 rc = send_response(conn, msg_not_found);
[9c3bba0]230 free(fname);
[0a549cc]231 return rc;
[4a4cc150]232 }
[0a549cc]233
[9c3bba0]234 free(fname);
[0a549cc]235
[fab2746]236 rc = send_response(conn, msg_ok);
[4a4cc150]237 if (rc != EOK)
238 return rc;
[0a549cc]239
[4a4cc150]240 while (true) {
[0a549cc]241 ssize_t nr = read(fd, fbuf, BUFFER_SIZE);
[4a4cc150]242 if (nr == 0)
243 break;
[0a549cc]244
[4a4cc150]245 if (nr < 0) {
246 close(fd);
247 return EIO;
248 }
[0a549cc]249
[fab2746]250 rc = tcp_conn_send(conn, fbuf, nr);
251 if (rc != EOK) {
252 fprintf(stderr, "tcp_conn_send() failed\n");
[4a4cc150]253 close(fd);
254 return rc;
255 }
256 }
[0a549cc]257
[4a4cc150]258 close(fd);
[0a549cc]259
[4a4cc150]260 return EOK;
261}
262
[fab2746]263static int req_process(tcp_conn_t *conn)
[4a4cc150]264{
[fab2746]265 int rc = recv_line(conn);
[4a4cc150]266 if (rc != EOK) {
[0a549cc]267 fprintf(stderr, "recv_line() failed\n");
[4a4cc150]268 return rc;
269 }
[0a549cc]270
[26b6789]271 if (verbose)
272 fprintf(stderr, "Request: %s", lbuf);
[0a549cc]273
[4a4cc150]274 if (str_lcmp(lbuf, "GET ", 4) != 0) {
[fab2746]275 rc = send_response(conn, msg_not_implemented);
[0a549cc]276 return rc;
[4a4cc150]277 }
[0a549cc]278
279 char *uri = lbuf + 4;
280 char *end_uri = str_chr(uri, ' ');
[4a4cc150]281 if (end_uri == NULL) {
282 end_uri = lbuf + lbuf_used - 2;
283 assert(*end_uri == '\r');
284 }
[0a549cc]285
[4a4cc150]286 *end_uri = '\0';
[26b6789]287 if (verbose)
288 fprintf(stderr, "Requested URI: %s\n", uri);
[0a549cc]289
[4a4cc150]290 if (!uri_is_valid(uri)) {
[fab2746]291 rc = send_response(conn, msg_bad_request);
[0a549cc]292 return rc;
[4a4cc150]293 }
[0a549cc]294
[fab2746]295 return uri_get(uri, conn);
[4a4cc150]296}
[f4a2d624]297
[0a549cc]298static void usage(void)
[f4a2d624]299{
[0a549cc]300 printf("Skeletal server\n"
301 "\n"
302 "Usage: " NAME " [options]\n"
303 "\n"
304 "Where options are:\n"
305 "-p port_number | --port=port_number\n"
306 "\tListening port (default " STRING(DEFAULT_PORT) ").\n"
307 "\n"
308 "-h | --help\n"
[26b6789]309 "\tShow this application help.\n"
310 "-v | --verbose\n"
311 "\tVerbose mode\n");
[0a549cc]312}
[f4a2d624]313
[0a549cc]314static int parse_option(int argc, char *argv[], int *index)
315{
316 int value;
[f4a2d624]317 int rc;
[0a549cc]318
319 switch (argv[*index][1]) {
320 case 'h':
321 usage();
322 exit(0);
323 break;
324 case 'p':
325 rc = arg_parse_int(argc, argv, index, &value, 0);
326 if (rc != EOK)
327 return rc;
328
329 port = (uint16_t) value;
330 break;
[26b6789]331 case 'v':
332 verbose = true;
333 break;
[0a549cc]334 /* Long options with double dash */
335 case '-':
336 if (str_lcmp(argv[*index] + 2, "help", 5) == 0) {
337 usage();
338 exit(0);
339 } else if (str_lcmp(argv[*index] + 2, "port=", 5) == 0) {
340 rc = arg_parse_int(argc, argv, index, &value, 7);
341 if (rc != EOK)
342 return rc;
343
344 port = (uint16_t) value;
[26b6789]345 } else if (str_cmp(argv[*index] +2, "verbose") == 0) {
346 verbose = true;
[0a549cc]347 } else {
348 usage();
349 return EINVAL;
350 }
351 break;
352 default:
353 usage();
354 return EINVAL;
355 }
356
357 return EOK;
358}
[f4a2d624]359
[fab2746]360static void websrv_new_conn(tcp_listener_t *lst, tcp_conn_t *conn)
361{
362 int rc;
363
364 if (verbose)
365 fprintf(stderr, "New connection, waiting for request\n");
366
367 rbuf_out = 0;
368 rbuf_in = 0;
369
370 rc = req_process(conn);
371 if (rc != EOK) {
372 fprintf(stderr, "Error processing request (%s)\n",
373 str_error(rc));
[b99f6e2]374 return;
375 }
376
377 rc = tcp_conn_send_fin(conn);
378 if (rc != EOK) {
379 fprintf(stderr, "Error sending FIN.\n");
380 return;
[fab2746]381 }
382}
383
[0a549cc]384int main(int argc, char *argv[])
385{
[fab2746]386 inet_ep_t ep;
387 tcp_listener_t *lst;
388 tcp_t *tcp;
389 int rc;
390
[0a549cc]391 /* Parse command line arguments */
392 for (int i = 1; i < argc; i++) {
393 if (argv[i][0] == '-') {
[fab2746]394 rc = parse_option(argc, argv, &i);
[0a549cc]395 if (rc != EOK)
396 return rc;
397 } else {
398 usage();
399 return EINVAL;
400 }
401 }
402
[26b6789]403 printf("%s: HelenOS web server\n", NAME);
404
405 if (verbose)
[fab2746]406 fprintf(stderr, "Creating listener\n");
[0a549cc]407
[fab2746]408 inet_ep_init(&ep);
409 ep.port = port;
410
411 rc = tcp_create(&tcp);
[f4a2d624]412 if (rc != EOK) {
[fab2746]413 fprintf(stderr, "Error initializing TCP.\n");
414 return 1;
[f4a2d624]415 }
[fab2746]416
417 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
418 &lst);
[f4a2d624]419 if (rc != EOK) {
[fab2746]420 fprintf(stderr, "Error creating listener.\n");
421 return 2;
[f4a2d624]422 }
[0a549cc]423
[26b6789]424 fprintf(stderr, "%s: Listening for connections at port %" PRIu16 "\n",
425 NAME, port);
426
427 task_retval(0);
[fab2746]428 async_manager();
[0a549cc]429
430 /* Not reached */
[f4a2d624]431 return 0;
432}
433
434/** @}
435 */
Note: See TracBrowser for help on using the repository browser.