source: mainline/uspace/app/websrv/websrv.c@ 3e67ab1

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

Based on popular vote, move websrv back into /app.

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