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