* Fix a typo in the dpkg changelog.
[deb_shairplay.git] / src / lib / httpd.c
... / ...
CommitLineData
1/**
2 * Copyright (C) 2011-2012 Juho Vähä-Herttua
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 */
14
15#include <stdlib.h>
16#include <string.h>
17#include <stdio.h>
18#include <assert.h>
19
20#include "httpd.h"
21#include "netutils.h"
22#include "http_request.h"
23#include "compat.h"
24#include "logger.h"
25
26struct http_connection_s {
27 int connected;
28
29 int socket_fd;
30 void *user_data;
31 http_request_t *request;
32};
33typedef struct http_connection_s http_connection_t;
34
35struct httpd_s {
36 logger_t *logger;
37 httpd_callbacks_t callbacks;
38
39 int max_connections;
40 int open_connections;
41 http_connection_t *connections;
42
43 /* These variables only edited mutex locked */
44 int running;
45 int joined;
46 thread_handle_t thread;
47 mutex_handle_t run_mutex;
48
49 /* Server fds for accepting connections */
50 int server_fd4;
51 int server_fd6;
52};
53
54httpd_t *
55httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections)
56{
57 httpd_t *httpd;
58
59 assert(logger);
60 assert(callbacks);
61 assert(max_connections > 0);
62
63 /* Allocate the httpd_t structure */
64 httpd = calloc(1, sizeof(httpd_t));
65 if (!httpd) {
66 return NULL;
67 }
68
69 httpd->max_connections = max_connections;
70 httpd->connections = calloc(max_connections, sizeof(http_connection_t));
71 if (!httpd->connections) {
72 free(httpd);
73 return NULL;
74 }
75
76 /* Use the logger provided */
77 httpd->logger = logger;
78
79 /* Save callback pointers */
80 memcpy(&httpd->callbacks, callbacks, sizeof(httpd_callbacks_t));
81
82 /* Initial status joined */
83 httpd->running = 0;
84 httpd->joined = 1;
85
86 return httpd;
87}
88
89void
90httpd_destroy(httpd_t *httpd)
91{
92 if (httpd) {
93 httpd_stop(httpd);
94
95 free(httpd->connections);
96 free(httpd);
97 }
98}
99
100static void
101httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len, unsigned char *remote, int remote_len)
102{
103 int i;
104
105 for (i=0; i<httpd->max_connections; i++) {
106 if (!httpd->connections[i].connected) {
107 break;
108 }
109 }
110 if (i == httpd->max_connections) {
111 /* This code should never be reached, we do not select server_fds when full */
112 logger_log(httpd->logger, LOGGER_INFO, "Max connections reached");
113 shutdown(fd, SHUT_RDWR);
114 closesocket(fd);
115 return;
116 }
117
118 httpd->open_connections++;
119 httpd->connections[i].socket_fd = fd;
120 httpd->connections[i].connected = 1;
121 httpd->connections[i].user_data = httpd->callbacks.conn_init(httpd->callbacks.opaque, local, local_len, remote, remote_len);
122}
123
124static int
125httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6)
126{
127 struct sockaddr_storage remote_saddr;
128 socklen_t remote_saddrlen;
129 struct sockaddr_storage local_saddr;
130 socklen_t local_saddrlen;
131 unsigned char *local, *remote;
132 int local_len, remote_len;
133 int ret, fd;
134
135 remote_saddrlen = sizeof(remote_saddr);
136 fd = accept(server_fd, (struct sockaddr *)&remote_saddr, &remote_saddrlen);
137 if (fd == -1) {
138 /* FIXME: Error happened */
139 return -1;
140 }
141
142 local_saddrlen = sizeof(local_saddr);
143 ret = getsockname(fd, (struct sockaddr *)&local_saddr, &local_saddrlen);
144 if (ret == -1) {
145 closesocket(fd);
146 return 0;
147 }
148
149 logger_log(httpd->logger, LOGGER_INFO, "Accepted %s client on socket %d",
150 (is_ipv6 ? "IPv6" : "IPv4"), fd);
151 local = netutils_get_address(&local_saddr, &local_len);
152 remote = netutils_get_address(&remote_saddr, &remote_len);
153
154 httpd_add_connection(httpd, fd, local, local_len, remote, remote_len);
155 return 1;
156}
157
158static void
159httpd_remove_connection(httpd_t *httpd, http_connection_t *connection)
160{
161 if (connection->request) {
162 http_request_destroy(connection->request);
163 connection->request = NULL;
164 }
165 httpd->callbacks.conn_destroy(connection->user_data);
166 shutdown(connection->socket_fd, SHUT_WR);
167 closesocket(connection->socket_fd);
168 connection->connected = 0;
169 httpd->open_connections--;
170}
171
172static THREAD_RETVAL
173httpd_thread(void *arg)
174{
175 httpd_t *httpd = arg;
176 char buffer[1024];
177 int i;
178
179 assert(httpd);
180
181 while (1) {
182 fd_set rfds;
183 struct timeval tv;
184 int nfds=0;
185 int ret;
186
187 MUTEX_LOCK(httpd->run_mutex);
188 if (!httpd->running) {
189 MUTEX_UNLOCK(httpd->run_mutex);
190 break;
191 }
192 MUTEX_UNLOCK(httpd->run_mutex);
193
194 /* Set timeout value to 5ms */
195 tv.tv_sec = 1;
196 tv.tv_usec = 5000;
197
198 /* Get the correct nfds value and set rfds */
199 FD_ZERO(&rfds);
200 if (httpd->open_connections < httpd->max_connections) {
201 if (httpd->server_fd4 != -1) {
202 FD_SET(httpd->server_fd4, &rfds);
203 if (nfds <= httpd->server_fd4) {
204 nfds = httpd->server_fd4+1;
205 }
206 }
207 if (httpd->server_fd6 != -1) {
208 FD_SET(httpd->server_fd6, &rfds);
209 if (nfds <= httpd->server_fd6) {
210 nfds = httpd->server_fd6+1;
211 }
212 }
213 }
214 for (i=0; i<httpd->max_connections; i++) {
215 int socket_fd;
216 if (!httpd->connections[i].connected) {
217 continue;
218 }
219 socket_fd = httpd->connections[i].socket_fd;
220 FD_SET(socket_fd, &rfds);
221 if (nfds <= socket_fd) {
222 nfds = socket_fd+1;
223 }
224 }
225
226 ret = select(nfds, &rfds, NULL, NULL, &tv);
227 if (ret == 0) {
228 /* Timeout happened */
229 continue;
230 } else if (ret == -1) {
231 /* FIXME: Error happened */
232 logger_log(httpd->logger, LOGGER_INFO, "Error in select");
233 break;
234 }
235
236 if (httpd->open_connections < httpd->max_connections &&
237 httpd->server_fd4 != -1 && FD_ISSET(httpd->server_fd4, &rfds)) {
238 ret = httpd_accept_connection(httpd, httpd->server_fd4, 0);
239 if (ret == -1) {
240 break;
241 } else if (ret == 0) {
242 continue;
243 }
244 }
245 if (httpd->open_connections < httpd->max_connections &&
246 httpd->server_fd6 != -1 && FD_ISSET(httpd->server_fd6, &rfds)) {
247 ret = httpd_accept_connection(httpd, httpd->server_fd6, 1);
248 if (ret == -1) {
249 break;
250 } else if (ret == 0) {
251 continue;
252 }
253 }
254 for (i=0; i<httpd->max_connections; i++) {
255 http_connection_t *connection = &httpd->connections[i];
256
257 if (!connection->connected) {
258 continue;
259 }
260 if (!FD_ISSET(connection->socket_fd, &rfds)) {
261 continue;
262 }
263
264 /* If not in the middle of request, allocate one */
265 if (!connection->request) {
266 connection->request = http_request_init();
267 assert(connection->request);
268 }
269
270 logger_log(httpd->logger, LOGGER_DEBUG, "Receiving on socket %d", connection->socket_fd);
271 ret = recv(connection->socket_fd, buffer, sizeof(buffer), 0);
272 if (ret == 0) {
273 logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d", connection->socket_fd);
274 httpd_remove_connection(httpd, connection);
275 continue;
276 }
277
278 /* Parse HTTP request from data read from connection */
279 http_request_add_data(connection->request, buffer, ret);
280 if (http_request_has_error(connection->request)) {
281 logger_log(httpd->logger, LOGGER_INFO, "Error in parsing: %s", http_request_get_error_name(connection->request));
282 httpd_remove_connection(httpd, connection);
283 continue;
284 }
285
286 /* If request is finished, process and deallocate */
287 if (http_request_is_complete(connection->request)) {
288 http_response_t *response = NULL;
289
290 httpd->callbacks.conn_request(connection->user_data, connection->request, &response);
291 http_request_destroy(connection->request);
292 connection->request = NULL;
293
294 if (response) {
295 const char *data;
296 int datalen;
297 int written;
298 int ret;
299
300 /* Get response data and datalen */
301 data = http_response_get_data(response, &datalen);
302
303 written = 0;
304 while (written < datalen) {
305 ret = send(connection->socket_fd, data+written, datalen-written, 0);
306 if (ret == -1) {
307 /* FIXME: Error happened */
308 logger_log(httpd->logger, LOGGER_INFO, "Error in sending data");
309 break;
310 }
311 written += ret;
312 }
313
314 if (http_response_get_disconnect(response)) {
315 logger_log(httpd->logger, LOGGER_INFO, "Disconnecting on software request");
316 httpd_remove_connection(httpd, connection);
317 }
318 } else {
319 logger_log(httpd->logger, LOGGER_INFO, "Didn't get response");
320 }
321 http_response_destroy(response);
322 } else {
323 logger_log(httpd->logger, LOGGER_DEBUG, "Request not complete, waiting for more data...");
324 }
325 }
326 }
327
328 /* Remove all connections that are still connected */
329 for (i=0; i<httpd->max_connections; i++) {
330 http_connection_t *connection = &httpd->connections[i];
331
332 if (!connection->connected) {
333 continue;
334 }
335 logger_log(httpd->logger, LOGGER_INFO, "Removing connection for socket %d", connection->socket_fd);
336 httpd_remove_connection(httpd, connection);
337 }
338
339 /* Close server sockets since they are not used any more */
340 if (httpd->server_fd4 != -1) {
341 closesocket(httpd->server_fd4);
342 httpd->server_fd4 = -1;
343 }
344 if (httpd->server_fd6 != -1) {
345 closesocket(httpd->server_fd6);
346 httpd->server_fd6 = -1;
347 }
348
349 logger_log(httpd->logger, LOGGER_INFO, "Exiting HTTP thread");
350
351 return 0;
352}
353
354int
355httpd_start(httpd_t *httpd, unsigned short *port)
356{
357 /* How many connection attempts are kept in queue */
358 int backlog = 5;
359
360 assert(httpd);
361 assert(port);
362
363 MUTEX_LOCK(httpd->run_mutex);
364 if (httpd->running || !httpd->joined) {
365 MUTEX_UNLOCK(httpd->run_mutex);
366 return 0;
367 }
368
369 httpd->server_fd4 = netutils_init_socket(port, 0, 0);
370 if (httpd->server_fd4 == -1) {
371 logger_log(httpd->logger, LOGGER_ERR, "Error initialising socket %d", SOCKET_GET_ERROR());
372 MUTEX_UNLOCK(httpd->run_mutex);
373 return -1;
374 }
375 httpd->server_fd6 = netutils_init_socket(port, 1, 0);
376 if (httpd->server_fd6 == -1) {
377 logger_log(httpd->logger, LOGGER_WARNING, "Error initialising IPv6 socket %d", SOCKET_GET_ERROR());
378 logger_log(httpd->logger, LOGGER_WARNING, "Continuing without IPv6 support");
379 }
380
381 if (httpd->server_fd4 != -1 && listen(httpd->server_fd4, backlog) == -1) {
382 logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv4 socket");
383 closesocket(httpd->server_fd4);
384 closesocket(httpd->server_fd6);
385 MUTEX_UNLOCK(httpd->run_mutex);
386 return -2;
387 }
388 if (httpd->server_fd6 != -1 && listen(httpd->server_fd6, backlog) == -1) {
389 logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv6 socket");
390 closesocket(httpd->server_fd4);
391 closesocket(httpd->server_fd6);
392 MUTEX_UNLOCK(httpd->run_mutex);
393 return -2;
394 }
395 logger_log(httpd->logger, LOGGER_INFO, "Initialized server socket(s)");
396
397 /* Set values correctly and create new thread */
398 httpd->running = 1;
399 httpd->joined = 0;
400 THREAD_CREATE(httpd->thread, httpd_thread, httpd);
401 MUTEX_UNLOCK(httpd->run_mutex);
402
403 return 1;
404}
405
406int
407httpd_is_running(httpd_t *httpd)
408{
409 int running;
410
411 assert(httpd);
412
413 MUTEX_LOCK(httpd->run_mutex);
414 running = httpd->running || !httpd->joined;
415 MUTEX_UNLOCK(httpd->run_mutex);
416
417 return running;
418}
419
420void
421httpd_stop(httpd_t *httpd)
422{
423 assert(httpd);
424
425 MUTEX_LOCK(httpd->run_mutex);
426 if (!httpd->running || httpd->joined) {
427 MUTEX_UNLOCK(httpd->run_mutex);
428 return;
429 }
430 httpd->running = 0;
431 MUTEX_UNLOCK(httpd->run_mutex);
432
433 THREAD_JOIN(httpd->thread);
434
435 MUTEX_LOCK(httpd->run_mutex);
436 httpd->joined = 1;
437 MUTEX_UNLOCK(httpd->run_mutex);
438}
439