2 * Copyright (C) 2011-2012 Juho Vähä-Herttua
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.
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.
22 #include "http_request.h"
26 struct http_connection_s
{
31 http_request_t
*request
;
33 typedef struct http_connection_s http_connection_t
;
37 httpd_callbacks_t callbacks
;
43 http_connection_t
*connections
;
45 /* These variables only edited mutex locked */
48 thread_handle_t thread
;
49 mutex_handle_t run_mutex
;
51 /* Server fd for accepting connections */
56 httpd_init(logger_t
*logger
, httpd_callbacks_t
*callbacks
, int max_connections
, int use_rtsp
)
62 assert(max_connections
> 0);
64 /* Allocate the httpd_t structure */
65 httpd
= calloc(1, sizeof(httpd_t
));
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
) {
78 /* Use the logger provided */
79 httpd
->logger
= logger
;
81 /* Save callback pointers */
82 memcpy(&httpd
->callbacks
, callbacks
, sizeof(httpd_callbacks_t
));
84 /* Initial status joined */
92 httpd_destroy(httpd_t
*httpd
)
97 free(httpd
->connections
);
103 httpd_add_connection(httpd_t
*httpd
, int fd
, unsigned char *local
, int local_len
, unsigned char *remote
, int remote_len
)
107 for (i
=0; i
<httpd
->max_connections
; i
++) {
108 if (!httpd
->connections
[i
].connected
) {
112 if (i
== httpd
->max_connections
) {
113 /* This code should never be reached, we do not select server_fd when full */
114 logger_log(httpd
->logger
, LOGGER_INFO
, "Max connections reached");
115 shutdown(fd
, SHUT_RDWR
);
120 httpd
->open_connections
++;
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
);
127 httpd_remove_connection(httpd_t
*httpd
, http_connection_t
*connection
)
129 if (connection
->request
) {
130 http_request_destroy(connection
->request
);
131 connection
->request
= NULL
;
133 httpd
->callbacks
.conn_destroy(connection
->user_data
);
134 shutdown(connection
->socket_fd
, SHUT_WR
);
135 closesocket(connection
->socket_fd
);
136 connection
->connected
= 0;
137 httpd
->open_connections
--;
141 httpd_thread(void *arg
)
143 httpd_t
*httpd
= arg
;
155 MUTEX_LOCK(httpd
->run_mutex
);
156 if (!httpd
->running
) {
157 MUTEX_UNLOCK(httpd
->run_mutex
);
160 MUTEX_UNLOCK(httpd
->run_mutex
);
162 /* Set timeout value to 5ms */
166 /* Get the correct nfds value and set rfds */
168 if (httpd
->open_connections
< httpd
->max_connections
) {
169 FD_SET(httpd
->server_fd
, &rfds
);
170 nfds
= httpd
->server_fd
+1;
172 for (i
=0; i
<httpd
->max_connections
; i
++) {
174 if (!httpd
->connections
[i
].connected
) {
177 socket_fd
= httpd
->connections
[i
].socket_fd
;
178 FD_SET(socket_fd
, &rfds
);
179 if (nfds
<= socket_fd
) {
184 ret
= select(nfds
, &rfds
, NULL
, NULL
, &tv
);
186 /* Timeout happened */
188 } else if (ret
== -1) {
189 /* FIXME: Error happened */
190 logger_log(httpd
->logger
, LOGGER_INFO
, "Error in select");
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
;
203 remote_saddrlen
= sizeof(remote_saddr
);
204 fd
= accept(httpd
->server_fd
, (struct sockaddr
*)&remote_saddr
, &remote_saddrlen
);
206 /* FIXME: Error happened */
210 local_saddrlen
= sizeof(local_saddr
);
211 ret
= getsockname(fd
, (struct sockaddr
*)&local_saddr
, &local_saddrlen
);
217 logger_log(httpd
->logger
, LOGGER_INFO
, "Accepted client on socket %d", fd
);
218 local
= netutils_get_address(&local_saddr
, &local_len
);
219 remote
= netutils_get_address(&remote_saddr
, &remote_len
);
221 httpd_add_connection(httpd
, fd
, local
, local_len
, remote
, remote_len
);
223 for (i
=0; i
<httpd
->max_connections
; i
++) {
224 http_connection_t
*connection
= &httpd
->connections
[i
];
226 if (!connection
->connected
) {
229 if (!FD_ISSET(connection
->socket_fd
, &rfds
)) {
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
);
239 logger_log(httpd
->logger
, LOGGER_DEBUG
, "Receiving on socket %d", connection
->socket_fd
);
240 ret
= recv(connection
->socket_fd
, buffer
, sizeof(buffer
), 0);
242 logger_log(httpd
->logger
, LOGGER_INFO
, "Connection closed for socket %d", connection
->socket_fd
);
243 httpd_remove_connection(httpd
, connection
);
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
)) {
250 logger_log(httpd
->logger
, LOGGER_INFO
, "Error in parsing: %s", http_request_get_error_name(connection
->request
));
251 httpd_remove_connection(httpd
, connection
);
255 /* If request is finished, process and deallocate */
256 if (http_request_is_complete(connection
->request
)) {
257 http_response_t
*response
= NULL
;
259 httpd
->callbacks
.conn_request(connection
->user_data
, connection
->request
, &response
);
260 http_request_destroy(connection
->request
);
261 connection
->request
= NULL
;
269 /* Get response data and datalen */
270 data
= http_response_get_data(response
, &datalen
);
273 while (written
< datalen
) {
274 ret
= send(connection
->socket_fd
, data
+written
, datalen
-written
, 0);
276 /* FIXME: Error happened */
277 logger_log(httpd
->logger
, LOGGER_INFO
, "Error in sending data");
283 logger_log(httpd
->logger
, LOGGER_INFO
, "Didn't get response");
285 http_response_destroy(response
);
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
];
294 if (!connection
->connected
) {
297 logger_log(httpd
->logger
, LOGGER_INFO
, "Removing connection for socket %d", connection
->socket_fd
);
298 httpd_remove_connection(httpd
, connection
);
301 logger_log(httpd
->logger
, LOGGER_INFO
, "Exiting HTTP thread");
307 httpd_start(httpd_t
*httpd
, unsigned short *port
)
312 MUTEX_LOCK(httpd
->run_mutex
);
313 if (httpd
->running
|| !httpd
->joined
) {
314 MUTEX_UNLOCK(httpd
->run_mutex
);
318 httpd
->server_fd
= netutils_init_socket(port
, 1, 0);
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);
324 if (httpd
->server_fd
== -1) {
325 logger_log(httpd
->logger
, LOGGER_INFO
, "Error initialising socket %d", SOCKET_GET_ERROR());
326 MUTEX_UNLOCK(httpd
->run_mutex
);
329 if (listen(httpd
->server_fd
, 5) == -1) {
330 logger_log(httpd
->logger
, LOGGER_INFO
, "Error listening to socket");
331 MUTEX_UNLOCK(httpd
->run_mutex
);
334 logger_log(httpd
->logger
, LOGGER_INFO
, "Initialized server socket");
336 /* Set values correctly and create new thread */
339 THREAD_CREATE(httpd
->thread
, httpd_thread
, httpd
);
340 MUTEX_UNLOCK(httpd
->run_mutex
);
346 httpd_is_running(httpd_t
*httpd
)
352 MUTEX_LOCK(httpd
->run_mutex
);
353 running
= httpd
->running
|| !httpd
->joined
;
354 MUTEX_UNLOCK(httpd
->run_mutex
);
360 httpd_stop(httpd_t
*httpd
)
364 MUTEX_LOCK(httpd
->run_mutex
);
365 if (!httpd
->running
|| httpd
->joined
) {
366 MUTEX_UNLOCK(httpd
->run_mutex
);
370 MUTEX_UNLOCK(httpd
->run_mutex
);
372 THREAD_JOIN(httpd
->thread
);
374 MUTEX_LOCK(httpd
->run_mutex
);
376 MUTEX_UNLOCK(httpd
->run_mutex
);