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
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 <stdbool.h>
37#include <errno.h>
38#include <assert.h>
39#include <stdio.h>
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <stdlib.h>
43#include <fcntl.h>
44#include <task.h>
45
46#include <inet/addr.h>
47#include <inet/endpoint.h>
48#include <inet/tcp.h>
49
50#include <arg_parse.h>
51#include <macros.h>
52#include <str.h>
53#include <str_error.h>
54
55#define NAME "websrv"
56
57#define DEFAULT_PORT 8080
58
59#define WEB_ROOT "/data/web"
60
61/** Buffer for receiving the request. */
62#define BUFFER_SIZE 1024
63
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
74static uint16_t port = DEFAULT_PORT;
75
76static char rbuf[BUFFER_SIZE];
77static size_t rbuf_out;
78static size_t rbuf_in;
79
80static char lbuf[BUFFER_SIZE + 1];
81static size_t lbuf_used;
82
83static char fbuf[BUFFER_SIZE];
84
85static bool verbose = false;
86
87/** Responses to send to client. */
88
89static const char *msg_ok =
90 "HTTP/1.0 200 OK\r\n"
91 "\r\n";
92
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
132/** Receive one character (with buffering) */
133static int recv_char(tcp_conn_t *conn, char *c)
134{
135 size_t nrecv;
136 int rc;
137
138 if (rbuf_out == rbuf_in) {
139 rbuf_out = 0;
140 rbuf_in = 0;
141
142 rc = tcp_conn_recv_wait(conn, rbuf, BUFFER_SIZE, &nrecv);
143 if (rc != EOK) {
144 fprintf(stderr, "tcp_conn_recv() failed (%d)\n", rc);
145 return rc;
146 }
147
148 rbuf_in = nrecv;
149 }
150
151 *c = rbuf[rbuf_out++];
152 return EOK;
153}
154
155/** Receive one line with length limit */
156static int recv_line(tcp_conn_t *conn)
157{
158 char *bp = lbuf;
159 char c = '\0';
160
161 while (bp < lbuf + BUFFER_SIZE) {
162 char prev = c;
163 int rc = recv_char(conn, &c);
164
165 if (rc != EOK)
166 return rc;
167
168 *bp++ = c;
169 if ((prev == '\r') && (c == '\n'))
170 break;
171 }
172
173 lbuf_used = bp - lbuf;
174 *bp = '\0';
175
176 if (bp == lbuf + BUFFER_SIZE)
177 return ELIMIT;
178
179 return EOK;
180}
181
182static bool uri_is_valid(char *uri)
183{
184 if (uri[0] != '/')
185 return false;
186
187 if (uri[1] == '.')
188 return false;
189
190 char *cp = uri + 1;
191
192 while (*cp != '\0') {
193 char c = *cp++;
194 if (c == '/')
195 return false;
196 }
197
198 return true;
199}
200
201static int send_response(tcp_conn_t *conn, const char *msg)
202{
203 size_t response_size = str_size(msg);
204
205 if (verbose)
206 fprintf(stderr, "Sending response\n");
207
208 int rc = tcp_conn_send(conn, (void *) msg, response_size);
209 if (rc != EOK) {
210 fprintf(stderr, "tcp_conn_send() failed\n");
211 return rc;
212 }
213
214 return EOK;
215}
216
217static int uri_get(const char *uri, tcp_conn_t *conn)
218{
219 if (str_cmp(uri, "/") == 0)
220 uri = "/index.html";
221
222 char *fname;
223 int rc = asprintf(&fname, "%s%s", WEB_ROOT, uri);
224 if (rc < 0)
225 return ENOMEM;
226
227 int fd = open(fname, O_RDONLY);
228 if (fd < 0) {
229 rc = send_response(conn, msg_not_found);
230 free(fname);
231 return rc;
232 }
233
234 free(fname);
235
236 rc = send_response(conn, msg_ok);
237 if (rc != EOK)
238 return rc;
239
240 while (true) {
241 ssize_t nr = read(fd, fbuf, BUFFER_SIZE);
242 if (nr == 0)
243 break;
244
245 if (nr < 0) {
246 close(fd);
247 return EIO;
248 }
249
250 rc = tcp_conn_send(conn, fbuf, nr);
251 if (rc != EOK) {
252 fprintf(stderr, "tcp_conn_send() failed\n");
253 close(fd);
254 return rc;
255 }
256 }
257
258 close(fd);
259
260 return EOK;
261}
262
263static int req_process(tcp_conn_t *conn)
264{
265 int rc = recv_line(conn);
266 if (rc != EOK) {
267 fprintf(stderr, "recv_line() failed\n");
268 return rc;
269 }
270
271 if (verbose)
272 fprintf(stderr, "Request: %s", lbuf);
273
274 if (str_lcmp(lbuf, "GET ", 4) != 0) {
275 rc = send_response(conn, msg_not_implemented);
276 return rc;
277 }
278
279 char *uri = lbuf + 4;
280 char *end_uri = str_chr(uri, ' ');
281 if (end_uri == NULL) {
282 end_uri = lbuf + lbuf_used - 2;
283 assert(*end_uri == '\r');
284 }
285
286 *end_uri = '\0';
287 if (verbose)
288 fprintf(stderr, "Requested URI: %s\n", uri);
289
290 if (!uri_is_valid(uri)) {
291 rc = send_response(conn, msg_bad_request);
292 return rc;
293 }
294
295 return uri_get(uri, conn);
296}
297
298static void usage(void)
299{
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"
309 "\tShow this application help.\n"
310 "-v | --verbose\n"
311 "\tVerbose mode\n");
312}
313
314static int parse_option(int argc, char *argv[], int *index)
315{
316 int value;
317 int rc;
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;
331 case 'v':
332 verbose = true;
333 break;
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;
345 } else if (str_cmp(argv[*index] +2, "verbose") == 0) {
346 verbose = true;
347 } else {
348 usage();
349 return EINVAL;
350 }
351 break;
352 default:
353 usage();
354 return EINVAL;
355 }
356
357 return EOK;
358}
359
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));
374 return;
375 }
376
377 rc = tcp_conn_send_fin(conn);
378 if (rc != EOK) {
379 fprintf(stderr, "Error sending FIN.\n");
380 return;
381 }
382}
383
384int main(int argc, char *argv[])
385{
386 inet_ep_t ep;
387 tcp_listener_t *lst;
388 tcp_t *tcp;
389 int rc;
390
391 /* Parse command line arguments */
392 for (int i = 1; i < argc; i++) {
393 if (argv[i][0] == '-') {
394 rc = parse_option(argc, argv, &i);
395 if (rc != EOK)
396 return rc;
397 } else {
398 usage();
399 return EINVAL;
400 }
401 }
402
403 printf("%s: HelenOS web server\n", NAME);
404
405 if (verbose)
406 fprintf(stderr, "Creating listener\n");
407
408 inet_ep_init(&ep);
409 ep.port = port;
410
411 rc = tcp_create(&tcp);
412 if (rc != EOK) {
413 fprintf(stderr, "Error initializing TCP.\n");
414 return 1;
415 }
416
417 rc = tcp_listener_create(tcp, &ep, &listen_cb, NULL, &conn_cb, NULL,
418 &lst);
419 if (rc != EOK) {
420 fprintf(stderr, "Error creating listener.\n");
421 return 2;
422 }
423
424 fprintf(stderr, "%s: Listening for connections at port %" PRIu16 "\n",
425 NAME, port);
426
427 task_retval(0);
428 async_manager();
429
430 /* Not reached */
431 return 0;
432}
433
434/** @}
435 */
Note: See TracBrowser for help on using the repository browser.