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