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