Simplify the set_log_callback, it is better for thread safety
[deb_shairplay.git] / src / lib / httpd.c
CommitLineData
23e7e3ae
JVH
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
2340bcd3
JVH
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 use_rtsp;
40
41 int max_connections;
c457d545 42 int open_connections;
2340bcd3
JVH
43 http_connection_t *connections;
44
45 /* These variables only edited mutex locked */
46 int running;
47 int joined;
48 thread_handle_t thread;
49 mutex_handle_t run_mutex;
50
870f342f
JVH
51 /* Server fds for accepting connections */
52 int server_fd4;
53 int server_fd6;
2340bcd3
JVH
54};
55
56httpd_t *
57httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int max_connections, int use_rtsp)
58{
59 httpd_t *httpd;
60
61 assert(logger);
62 assert(callbacks);
63 assert(max_connections > 0);
64
65 /* Allocate the httpd_t structure */
66 httpd = calloc(1, sizeof(httpd_t));
67 if (!httpd) {
68 return NULL;
69 }
70
71 httpd->use_rtsp = !!use_rtsp;
72 httpd->max_connections = max_connections;
73 httpd->connections = calloc(max_connections, sizeof(http_connection_t));
74 if (!httpd->connections) {
75 free(httpd);
76 return NULL;
77 }
78
79 /* Use the logger provided */
80 httpd->logger = logger;
81
82 /* Save callback pointers */
83 memcpy(&httpd->callbacks, callbacks, sizeof(httpd_callbacks_t));
84
85 /* Initial status joined */
86 httpd->running = 0;
87 httpd->joined = 1;
88
89 return httpd;
90}
91
92void
93httpd_destroy(httpd_t *httpd)
94{
95 if (httpd) {
96 httpd_stop(httpd);
97
98 free(httpd->connections);
99 free(httpd);
100 }
101}
102
103static void
104httpd_add_connection(httpd_t *httpd, int fd, unsigned char *local, int local_len, unsigned char *remote, int remote_len)
105{
106 int i;
107
108 for (i=0; i<httpd->max_connections; i++) {
109 if (!httpd->connections[i].connected) {
110 break;
111 }
112 }
113 if (i == httpd->max_connections) {
870f342f 114 /* This code should never be reached, we do not select server_fds when full */
46212791 115 logger_log(httpd->logger, LOGGER_INFO, "Max connections reached");
2340bcd3
JVH
116 shutdown(fd, SHUT_RDWR);
117 closesocket(fd);
118 return;
119 }
120
c457d545 121 httpd->open_connections++;
2340bcd3
JVH
122 httpd->connections[i].socket_fd = fd;
123 httpd->connections[i].connected = 1;
124 httpd->connections[i].user_data = httpd->callbacks.conn_init(httpd->callbacks.opaque, local, local_len, remote, remote_len);
125}
126
870f342f
JVH
127static int
128httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6)
129{
130 struct sockaddr_storage remote_saddr;
131 socklen_t remote_saddrlen;
132 struct sockaddr_storage local_saddr;
133 socklen_t local_saddrlen;
134 unsigned char *local, *remote;
135 int local_len, remote_len;
136 int ret, fd;
137
138 remote_saddrlen = sizeof(remote_saddr);
139 fd = accept(server_fd, (struct sockaddr *)&remote_saddr, &remote_saddrlen);
140 if (fd == -1) {
141 /* FIXME: Error happened */
142 return -1;
143 }
144
145 local_saddrlen = sizeof(local_saddr);
146 ret = getsockname(fd, (struct sockaddr *)&local_saddr, &local_saddrlen);
147 if (ret == -1) {
148 closesocket(fd);
149 return 0;
150 }
151
152 logger_log(httpd->logger, LOGGER_INFO, "Accepted %s client on socket %d",
153 (is_ipv6 ? "IPv6" : "IPv4"), fd);
154 local = netutils_get_address(&local_saddr, &local_len);
155 remote = netutils_get_address(&remote_saddr, &remote_len);
156
157 httpd_add_connection(httpd, fd, local, local_len, remote, remote_len);
158 return 1;
159}
160
2340bcd3
JVH
161static void
162httpd_remove_connection(httpd_t *httpd, http_connection_t *connection)
163{
164 if (connection->request) {
165 http_request_destroy(connection->request);
166 connection->request = NULL;
167 }
168 httpd->callbacks.conn_destroy(connection->user_data);
169 shutdown(connection->socket_fd, SHUT_WR);
170 closesocket(connection->socket_fd);
171 connection->connected = 0;
c457d545 172 httpd->open_connections--;
2340bcd3
JVH
173}
174
175static THREAD_RETVAL
176httpd_thread(void *arg)
177{
178 httpd_t *httpd = arg;
179 char buffer[1024];
180 int i;
181
182 assert(httpd);
183
184 while (1) {
185 fd_set rfds;
186 struct timeval tv;
187 int nfds=0;
188 int ret;
189
190 MUTEX_LOCK(httpd->run_mutex);
191 if (!httpd->running) {
192 MUTEX_UNLOCK(httpd->run_mutex);
193 break;
194 }
195 MUTEX_UNLOCK(httpd->run_mutex);
196
197 /* Set timeout value to 5ms */
198 tv.tv_sec = 1;
199 tv.tv_usec = 5000;
200
201 /* Get the correct nfds value and set rfds */
202 FD_ZERO(&rfds);
c457d545 203 if (httpd->open_connections < httpd->max_connections) {
870f342f
JVH
204 FD_SET(httpd->server_fd4, &rfds);
205 nfds = httpd->server_fd4+1;
206 if (httpd->server_fd6 != -1) {
207 FD_SET(httpd->server_fd6, &rfds);
208 if (nfds <= httpd->server_fd6) {
209 nfds = httpd->server_fd6+1;
210 }
211 }
c457d545 212 }
2340bcd3
JVH
213 for (i=0; i<httpd->max_connections; i++) {
214 int socket_fd;
215 if (!httpd->connections[i].connected) {
216 continue;
217 }
218 socket_fd = httpd->connections[i].socket_fd;
219 FD_SET(socket_fd, &rfds);
220 if (nfds <= socket_fd) {
221 nfds = socket_fd+1;
222 }
223 }
224
225 ret = select(nfds, &rfds, NULL, NULL, &tv);
226 if (ret == 0) {
227 /* Timeout happened */
228 continue;
229 } else if (ret == -1) {
230 /* FIXME: Error happened */
46212791 231 logger_log(httpd->logger, LOGGER_INFO, "Error in select");
2340bcd3
JVH
232 break;
233 }
234
870f342f
JVH
235 if (FD_ISSET(httpd->server_fd4, &rfds)) {
236 ret = httpd_accept_connection(httpd, httpd->server_fd4, 0);
237 if (ret == -1) {
2340bcd3 238 break;
870f342f
JVH
239 } else if (ret == 0) {
240 continue;
2340bcd3 241 }
870f342f 242 }
2e66aa96 243 if (httpd->server_fd6 != -1 && FD_ISSET(httpd->server_fd6, &rfds)) {
870f342f 244 ret = httpd_accept_connection(httpd, httpd->server_fd6, 1);
2340bcd3 245 if (ret == -1) {
870f342f
JVH
246 break;
247 } else if (ret == 0) {
2340bcd3
JVH
248 continue;
249 }
2340bcd3
JVH
250 }
251 for (i=0; i<httpd->max_connections; i++) {
252 http_connection_t *connection = &httpd->connections[i];
253
254 if (!connection->connected) {
255 continue;
256 }
257 if (!FD_ISSET(connection->socket_fd, &rfds)) {
258 continue;
259 }
260
261 /* If not in the middle of request, allocate one */
262 if (!connection->request) {
263 connection->request = http_request_init(httpd->use_rtsp);
264 assert(connection->request);
265 }
266
46212791 267 logger_log(httpd->logger, LOGGER_DEBUG, "Receiving on socket %d", connection->socket_fd);
2340bcd3
JVH
268 ret = recv(connection->socket_fd, buffer, sizeof(buffer), 0);
269 if (ret == 0) {
46212791 270 logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d", connection->socket_fd);
2340bcd3
JVH
271 httpd_remove_connection(httpd, connection);
272 continue;
273 }
274
275 /* Parse HTTP request from data read from connection */
276 http_request_add_data(connection->request, buffer, ret);
277 if (http_request_has_error(connection->request)) {
46212791 278 logger_log(httpd->logger, LOGGER_INFO, "Error in parsing: %s", http_request_get_error_name(connection->request));
2340bcd3
JVH
279 httpd_remove_connection(httpd, connection);
280 continue;
281 }
282
283 /* If request is finished, process and deallocate */
284 if (http_request_is_complete(connection->request)) {
285 http_response_t *response = NULL;
286
287 httpd->callbacks.conn_request(connection->user_data, connection->request, &response);
288 http_request_destroy(connection->request);
289 connection->request = NULL;
290
291 if (response) {
292 const char *data;
293 int datalen;
294 int written;
295 int ret;
296
297 /* Get response data and datalen */
298 data = http_response_get_data(response, &datalen);
299
300 written = 0;
301 while (written < datalen) {
302 ret = send(connection->socket_fd, data+written, datalen-written, 0);
303 if (ret == -1) {
304 /* FIXME: Error happened */
46212791 305 logger_log(httpd->logger, LOGGER_INFO, "Error in sending data");
2340bcd3
JVH
306 break;
307 }
308 written += ret;
309 }
310 } else {
46212791 311 logger_log(httpd->logger, LOGGER_INFO, "Didn't get response");
2340bcd3
JVH
312 }
313 http_response_destroy(response);
314 }
315 }
316 }
317
318 /* Remove all connections that are still connected */
319 for (i=0; i<httpd->max_connections; i++) {
320 http_connection_t *connection = &httpd->connections[i];
321
322 if (!connection->connected) {
323 continue;
324 }
46212791 325 logger_log(httpd->logger, LOGGER_INFO, "Removing connection for socket %d", connection->socket_fd);
2340bcd3
JVH
326 httpd_remove_connection(httpd, connection);
327 }
328
46212791 329 logger_log(httpd->logger, LOGGER_INFO, "Exiting HTTP thread");
2340bcd3
JVH
330
331 return 0;
332}
333
334int
335httpd_start(httpd_t *httpd, unsigned short *port)
336{
337 assert(httpd);
338 assert(port);
339
340 MUTEX_LOCK(httpd->run_mutex);
341 if (httpd->running || !httpd->joined) {
342 MUTEX_UNLOCK(httpd->run_mutex);
343 return 0;
344 }
345
870f342f
JVH
346 httpd->server_fd4 = netutils_init_socket(port, 0, 0);
347 if (httpd->server_fd4 == -1) {
348 logger_log(httpd->logger, LOGGER_ERR, "Error initialising socket %d", SOCKET_GET_ERROR());
2340bcd3
JVH
349 MUTEX_UNLOCK(httpd->run_mutex);
350 return -1;
351 }
870f342f
JVH
352 httpd->server_fd6 = netutils_init_socket(port, 1, 0);
353 if (httpd->server_fd6 == -1) {
354 logger_log(httpd->logger, LOGGER_WARNING, "Error initialising IPv6 socket %d", SOCKET_GET_ERROR());
355 logger_log(httpd->logger, LOGGER_WARNING, "Continuing without IPv6 support");
356 }
357
358 if (listen(httpd->server_fd4, 5) == -1) {
359 logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv4 socket");
360 closesocket(httpd->server_fd4);
361 closesocket(httpd->server_fd6);
362 MUTEX_UNLOCK(httpd->run_mutex);
363 return -2;
364 }
365 if (httpd->server_fd6 != -1 && listen(httpd->server_fd6, 5) == -1) {
366 logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv6 socket");
367 closesocket(httpd->server_fd4);
368 closesocket(httpd->server_fd6);
2340bcd3
JVH
369 MUTEX_UNLOCK(httpd->run_mutex);
370 return -2;
371 }
870f342f 372 logger_log(httpd->logger, LOGGER_INFO, "Initialized server socket(s)");
2340bcd3
JVH
373
374 /* Set values correctly and create new thread */
375 httpd->running = 1;
376 httpd->joined = 0;
377 THREAD_CREATE(httpd->thread, httpd_thread, httpd);
378 MUTEX_UNLOCK(httpd->run_mutex);
379
380 return 1;
381}
382
5a746b97
JVH
383int
384httpd_is_running(httpd_t *httpd)
385{
386 int running;
387
388 assert(httpd);
389
390 MUTEX_LOCK(httpd->run_mutex);
391 running = httpd->running || !httpd->joined;
392 MUTEX_UNLOCK(httpd->run_mutex);
393
394 return running;
395}
396
2340bcd3
JVH
397void
398httpd_stop(httpd_t *httpd)
399{
400 assert(httpd);
401
402 MUTEX_LOCK(httpd->run_mutex);
403 if (!httpd->running || httpd->joined) {
404 MUTEX_UNLOCK(httpd->run_mutex);
405 return;
406 }
407 httpd->running = 0;
408 MUTEX_UNLOCK(httpd->run_mutex);
409
410 THREAD_JOIN(httpd->thread);
411
412 MUTEX_LOCK(httpd->run_mutex);
413 httpd->joined = 1;
414 MUTEX_UNLOCK(httpd->run_mutex);
415}
416