major fixes and better tests
[webserver.git] / webserver.c
1 /* depend: */
2 /* cflags: */
3 /* linker: color.o debug.o file.o http.o server.o */
4
5 #include <assert.h>
6 #include <dirent.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10
11 #include "debug.h"
12 #include "http.h"
13 #include "server.h"
14
15 /* types */
16
17 /* constants */
18
19 #define BUFFER_SIZE 4096
20 #define ROOT_DIR "webroot"
21 #define SERVER_NAME "localhost"
22 #define CHARSET "iso-8859-1"
23
24 /* macros */
25
26 /* gobal variables */
27
28 char *progname = NULL;
29 int port = 8080;
30 char *root = ROOT_DIR;
31 char *servername = SERVER_NAME;
32 char *charset = CHARSET;
33
34 /* help function */
35
36 int usage (int ret)
37 {
38 FILE *fid = ret ? stderr : stdout;
39 fprintf (fid, "usage: %s\n", progname);
40 fprintf (fid, " -c : charset name (%s)\n", charset);
41 fprintf (fid, " -h : help message\n");
42 fprintf (fid, " -p : port number (%d)\n", port);
43 fprintf (fid, " -r : web root directory (%s)\n", root);
44 fprintf (fid, " -s : server name (%s)\n", servername);
45 fprintf (fid, " -v : verbose level (%d)\n", verbose);
46
47 return ret;
48 }
49
50 /* main function */
51
52 int main (int argc, char *argv[])
53 {
54 int i = 0;
55
56 /* program name */
57
58 progname = argv[0];
59 while (progname[i] != '\0') {
60 if ((progname[i] == '/') || (progname[i] == '\\')) {
61 progname += i + 1;
62 i = 0;
63 } else {
64 i++;
65 }
66 }
67
68 /* argument processing */
69
70 while (argc-- > 1) {
71 char *arg = *(++argv);
72 if (arg[0] != '-') {
73 VERBOSE (ERROR, PERROR ("%s: invalid option -- '%s'\n", progname, arg); usage (1));
74 return 1;
75 }
76 char c = arg[1];
77 switch (c) {
78 case 'c':
79 arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
80 if (arg == NULL) {
81 VERBOSE (ERROR, PERROR ("%s: missing charset name\n", progname); usage (1));
82 return 1;
83 }
84 charset = arg;
85 break;
86 case 'p':
87 arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
88 if (arg == NULL) {
89 VERBOSE (ERROR, PERROR ("%s: missing port number\n", progname); usage (1));
90 return 1;
91 }
92 port = atoi (arg);
93 if (port <= 0) {
94 VERBOSE (ERROR, PERROR ("%s: incorrect port number (%s)\n", progname, arg); usage (1));
95 return 1;
96 }
97 break;
98 case 's':
99 arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
100 if (arg == NULL) {
101 VERBOSE (ERROR, PERROR ("%s: missing server name\n", progname); usage (1));
102 return 1;
103 }
104 servername = arg;
105 break;
106 case 'r':
107 arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
108 if (arg == NULL) {
109 VERBOSE (ERROR, PERROR ("%s: missing directory name\n", progname); usage (1));
110 return 1;
111 }
112 root = arg;
113 break;
114 case 'v':
115 arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL;
116 if (arg == NULL) {
117 VERBOSE (ERROR, PERROR ("%s: missing verbose level\n", progname); usage (1));
118 return 1;
119 }
120 verbose = atoi (arg);
121 break;
122 case 'h':
123 default:
124 return usage (c != 'h');
125 }
126 }
127
128 /* check root directory */
129 VERBOSE (DEBUG, PRINT ("Check web root\n"));
130 DIR *pdir = opendir (root);
131 if (pdir == NULL) {
132 VERBOSE (ERROR, PERROR ("Can't read directory (%s)\n", root));
133 return 1;
134 }
135 closedir (pdir);
136
137 /* configuration */
138 conf_t conf = {root, servername, charset};
139
140 /* init network stack */
141 VERBOSE (DEBUG, PRINT ("Initializing socket\n"));
142 init_network_context ();
143 if (open_listening_socket (port) == 0) {
144 VERBOSE (ERROR, PERROR ("Can't open listening socket\n"));
145 return 1;
146 }
147 VERBOSE (INFO, PRINT ("Listening socket on port %d\n", port));
148
149 /* main loop */
150 while (1) {
151 if (accept_incoming_connection () == 0) {
152 usleep (1e5);
153 continue;
154 }
155
156 VERBOSE (DEBUG, PRINT ("Server connected, waiting for data\n"));
157
158 char *data = NULL;
159 char *output = NULL;
160
161 int len = receive_data (&data);
162 if (len == 0) {
163 VERBOSE (WARNING, PRINT ("Connection closed by peer (rx)\n"));
164 } else if (len < 0) {
165 VERBOSE (WARNING, PRINT ("Connection in error (rx)\n"));
166 } else {
167 VERBOSE (DEBUG, PRINT ("Received %d bytes\n", len));
168
169 // processing
170 VERBOSE (DEBUG, PRINT ("Processing %s\n", data));
171 len = processing (data, len, &conf, &output);
172
173 VERBOSE (DEBUG, PRINT ("Sending data (%d)\n%s\n", len, data));
174 int rc = send_data (output, len);
175 if (rc == 0) {
176 VERBOSE (WARNING, PRINT ("Connection closed by peer (tx)\n"));
177 } else if (rc < 0) {
178 VERBOSE (WARNING, PRINT ("Connection in error (tx)\n"));
179 }
180 }
181
182 VERBOSE (DEBUG, PRINT ("Closing connection\n"));
183 if (data) {
184 free (data);
185 }
186 if (output) {
187 free (output);
188 }
189 close_connection ();
190 }
191
192 VERBOSE (DEBUG, PRINT ("Closing socket\n"));
193 terminate_network_context ();
194
195 return 2;
196 }
197
198 // test: webserver.exe -h
199 // test: webserver.exe -h | awk '/usage:/ { rc=1 } END { exit (1-rc) }'
200 // test: webserver.exe -_ 2> /dev/null | wc -l | xargs test 0 =
201 // test: webserver.exe -_ 2>&1 | awk '/usage:/ { rc=1 } END { exit (1-rc) }'
202 // test: webserver.exe error 2>&1 | grep -q 'invalid option'
203 // test: webserver.exe -v 2>&1 | grep -q 'missing verbose level'
204 // test: webserver.exe -c 2>&1 | grep -q 'missing charset name'
205 // test: webserver.exe -p 2>&1 | grep -q 'missing port number'
206 // test: webserver.exe -p -1 2>&1 | grep -q 'incorrect port number'
207 // test: webserver.exe -s 2>&1 | grep -q 'missing server name'
208 // test: webserver.exe -r 2>&1 | grep -q 'missing directory name'
209 // test: webserver.exe >& test.log & pid=$!; sleep 1; kill -ABRT $pid; sleep 1; grep -q 'Listening socket on port 8080' test.log
210 // test: webserver.exe -p 8000 >& test.log & pid=$!; sleep 1; kill -TERM $pid; sleep 1; grep -q 'Listening socket on port 8000' test.log
211 // test: webserver.exe -p 8001 -c iso-8859-1 -r webroot -s localhost >&/dev/null & pid=$!; sleep 1; curl http://localhost:8001/index.html > test.log; kill -TERM $pid; sleep 1; grep -q '<title>Test</title>' test.log
212 // test: webserver.exe -v 3 -p 8002 >&/dev/null & pid=$!; sleep 1; curl -v http://localhost:8002/index.html >& test.log; kill -TERM $pid; sleep 1; grep -q 200 test.log
213 // test: webserver.exe -v 3 -p 8003 >&/dev/null & pid=$!; sleep 1; curl -v http://localhost:8003/not_found.html >& test.log; kill -TERM $pid; sleep 1; grep -q 404 test.log
214 // test: webserver.exe -v 3 -p 8004 >&/dev/null & pid=$!; sleep 1; curl -v -I http://localhost:8004/index.html >& test.log; kill -TERM $pid; sleep 1; grep -q Content-Length test.log; test $? -eq 1
215 // test: webserver.exe -v 3 -p 8005 >&/dev/null & pid=$!; sleep 1; curl -v -d '' http://localhost:8005/index.html >& test.log; kill -TERM $pid; sleep 1; grep -q '<title>Test</title>' test.log
216
217 /* vim: set ts=4 sw=4 et: */