1 /*-
2 * Copyright (c) 2005-2006 Robert N. M. Watson
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 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27 #include <sys/types.h>
28 #include <sys/mman.h>
29 #include <sys/socket.h>
30 #include <sys/uio.h>
31 #include <sys/utsname.h>
32 #include <sys/wait.h>
33
34 #include <netinet/in.h>
35
36 #include <arpa/inet.h>
37
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <pthread.h>
43 #include <signal.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <sysexits.h>
48 #include <unistd.h>
49
50 static int threaded; /* 1 for threaded, 0 for forked. */
51
52 /*
53 * Simple, multi-threaded/multi-process HTTP server. Very dumb.
54 *
55 * If a path is specified as an argument, only that file is served. If no
56 * path is specified, httpd will create one file to send per server thread.
57 */
58 #define THREADS 128
59 #define BUFFER 1024
60 #define FILESIZE 1024
61
62 #define HTTP_OK "HTTP/1.1 200 OK\n"
63 #define HTTP_SERVER1 "Server rwatson_httpd/1.0 ("
64 #define HTTP_SERVER2 ")\n"
65 #define HTTP_CONNECTION "Connection: close\n"
66 #define HTTP_CONTENT "Content-Type: text/html\n\n"
67
68 /*
69 * In order to support both multi-threaded and multi-process operation but
70 * use a single shared memory statistics model, we create a page-aligned
71 * statistics buffer. For threaded operation, it's just shared memory due to
72 * threading; for multi-process operation, we mark it as INHERIT_SHARE, so we
73 * must put it in page-aligned memory that isn't shared with other memory, or
74 * risk accidental sharing of other statep.
75 */
76 static struct state {
77 struct httpd_thread_statep {
78 pthread_t hts_thread; /* Multi-thread. */
79 pid_t hts_pid; /* Multi-process. */
80 int hts_fd;
81 } hts[THREADS];
82
83 const char *path;
84 int data_file;
85 int listen_sock;
86 struct utsname utsname;
87 } *statep;
88
89 /*
90 * Borrowed from sys/param.h.
91 */
92 #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) /* to any y */
93
94 /*
95 * Given an open client socket, process its request. No notion of timeout.
96 */
97 static int
http_serve(int sock,int fd)98 http_serve(int sock, int fd)
99 {
100 struct iovec header_iovec[6];
101 struct sf_hdtr sf_hdtr;
102 char buffer[BUFFER];
103 ssize_t len;
104 int i, ncount;
105
106 /* Read until \n\n. Not very smart. */
107 ncount = 0;
108 while (1) {
109 len = recv(sock, buffer, BUFFER, 0);
110 if (len < 0) {
111 warn("recv");
112 return (-1);
113 }
114 if (len == 0)
115 return (-1);
116 for (i = 0; i < len; i++) {
117 switch (buffer[i]) {
118 case '\n':
119 ncount++;
120 break;
121
122 case '\r':
123 break;
124
125 default:
126 ncount = 0;
127 }
128 }
129 if (ncount == 2)
130 break;
131 }
132
133 bzero(&sf_hdtr, sizeof(sf_hdtr));
134 bzero(&header_iovec, sizeof(header_iovec));
135 header_iovec[0].iov_base = HTTP_OK;
136 header_iovec[0].iov_len = strlen(HTTP_OK);
137 header_iovec[1].iov_base = HTTP_SERVER1;
138 header_iovec[1].iov_len = strlen(HTTP_SERVER1);
139 header_iovec[2].iov_base = statep->utsname.sysname;
140 header_iovec[2].iov_len = strlen(statep->utsname.sysname);
141 header_iovec[3].iov_base = HTTP_SERVER2;
142 header_iovec[3].iov_len = strlen(HTTP_SERVER2);
143 header_iovec[4].iov_base = HTTP_CONNECTION;
144 header_iovec[4].iov_len = strlen(HTTP_CONNECTION);
145 header_iovec[5].iov_base = HTTP_CONTENT;
146 header_iovec[5].iov_len = strlen(HTTP_CONTENT);
147 sf_hdtr.headers = header_iovec;
148 sf_hdtr.hdr_cnt = 6;
149 sf_hdtr.trailers = NULL;
150 sf_hdtr.trl_cnt = 0;
151
152 if (sendfile(fd, sock, 0, 0, &sf_hdtr, NULL, 0) < 0)
153 warn("sendfile");
154
155 return (0);
156 }
157
158 static void *
httpd_worker(void * arg)159 httpd_worker(void *arg)
160 {
161 struct httpd_thread_statep *htsp;
162 int sock;
163
164 htsp = arg;
165
166 while (1) {
167 sock = accept(statep->listen_sock, NULL, NULL);
168 if (sock < 0)
169 continue;
170 (void)http_serve(sock, htsp->hts_fd);
171 close(sock);
172 }
173 }
174
175 static void
killall(void)176 killall(void)
177 {
178 int i;
179
180 for (i = 0; i < THREADS; i++) {
181 if (statep->hts[i].hts_pid != 0)
182 (void)kill(statep->hts[i].hts_pid, SIGTERM);
183 }
184 }
185
186 static void
usage(void)187 usage(void)
188 {
189
190 fprintf(stderr, "httpd [-t] port [path]\n");
191 exit(EX_USAGE);
192 }
193
194 int
main(int argc,char * argv[])195 main(int argc, char *argv[])
196 {
197 u_char filebuffer[FILESIZE];
198 char temppath[PATH_MAX];
199 struct sockaddr_in sin;
200 int ch, error, i;
201 char *pagebuffer;
202 ssize_t len;
203 pid_t pid;
204
205
206 while ((ch = getopt(argc, argv, "t")) != -1) {
207 switch (ch) {
208 case 't':
209 threaded = 1;
210 break;
211
212 default:
213 usage();
214 }
215 }
216 argc -= optind;
217 argv += optind;
218
219 if (argc != 1 && argc != 2)
220 usage();
221
222 len = roundup(sizeof(struct state), getpagesize());
223 pagebuffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_ANON, -1, 0);
224 if (pagebuffer == MAP_FAILED)
225 err(-1, "mmap");
226 if (minherit(pagebuffer, len, INHERIT_SHARE) < 0)
227 err(-1, "minherit");
228 statep = (struct state *)pagebuffer;
229
230 if (uname(&statep->utsname) < 0)
231 err(-1, "utsname");
232
233 statep->listen_sock = socket(PF_INET, SOCK_STREAM, 0);
234 if (statep->listen_sock < 0)
235 err(-1, "socket(PF_INET, SOCK_STREAM)");
236
237 bzero(&sin, sizeof(sin));
238 sin.sin_len = sizeof(sin);
239 sin.sin_family = AF_INET;
240 sin.sin_port = htons(atoi(argv[0]));
241
242 /*
243 * If a path is specified, use it. Otherwise, create temporary files
244 * with some data for each thread.
245 */
246 statep->path = argv[1];
247 if (statep->path != NULL) {
248 statep->data_file = open(statep->path, O_RDONLY);
249 if (statep->data_file < 0)
250 err(-1, "open: %s", statep->path);
251 for (i = 0; i < THREADS; i++)
252 statep->hts[i].hts_fd = statep->data_file;
253 } else {
254 memset(filebuffer, 'A', FILESIZE - 1);
255 filebuffer[FILESIZE - 1] = '\n';
256 for (i = 0; i < THREADS; i++) {
257 snprintf(temppath, PATH_MAX, "/tmp/httpd.XXXXXXXXXXX");
258 statep->hts[i].hts_fd = mkstemp(temppath);
259 if (statep->hts[i].hts_fd < 0)
260 err(-1, "mkstemp");
261 (void)unlink(temppath);
262 len = write(statep->hts[i].hts_fd, filebuffer,
263 FILESIZE);
264 if (len < 0)
265 err(-1, "write");
266 if (len < FILESIZE)
267 errx(-1, "write: short");
268 }
269 }
270
271 if (bind(statep->listen_sock, (struct sockaddr *)&sin,
272 sizeof(sin)) < 0)
273 err(-1, "bind");
274
275 if (listen(statep->listen_sock, -1) < 0)
276 err(-1, "listen");
277
278 for (i = 0; i < THREADS; i++) {
279 if (threaded) {
280 if (pthread_create(&statep->hts[i].hts_thread, NULL,
281 httpd_worker, &statep->hts[i]) != 0)
282 err(-1, "pthread_create");
283 } else {
284 pid = fork();
285 if (pid < 0) {
286 error = errno;
287 killall();
288 errno = error;
289 err(-1, "fork");
290 }
291 if (pid == 0)
292 httpd_worker(&statep->hts[i]);
293 statep->hts[i].hts_pid = pid;
294 }
295 }
296
297 for (i = 0; i < THREADS; i++) {
298 if (threaded) {
299 if (pthread_join(statep->hts[i].hts_thread, NULL)
300 != 0)
301 err(-1, "pthread_join");
302 } else {
303 pid = waitpid(statep->hts[i].hts_pid, NULL, 0);
304 if (pid == statep->hts[i].hts_pid)
305 statep->hts[i].hts_pid = 0;
306 }
307 }
308 if (!threaded)
309 killall();
310 return (0);
311 }
312