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