source: mainline/uspace/app/websrv/websrv.c@ 20de14d

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 20de14d was 0a549cc, checked in by Martin Decky <martin@…>, 14 years ago

improve web server

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