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
;
41 http_connection_t
*connections
;
43 /* These variables only edited mutex locked */
46 thread_handle_t thread
;
47 mutex_handle_t run_mutex
;
49 /* Server fds for accepting connections */
55 httpd_init(logger_t
*logger
, httpd_callbacks_t
*callbacks
, int max_connections
)
61 assert(max_connections
> 0);
63 /* Allocate the httpd_t structure */
64 httpd
= calloc(1, sizeof(httpd_t
));
69 httpd
->max_connections
= max_connections
;
70 httpd
->connections
= calloc(max_connections
, sizeof(http_connection_t
));
71 if (!httpd
->connections
) {
76 /* Use the logger provided */
77 httpd
->logger
= logger
;
79 /* Save callback pointers */
80 memcpy(&httpd
->callbacks
, callbacks
, sizeof(httpd_callbacks_t
));
82 /* Initial status joined */
90 httpd_destroy(httpd_t
*httpd
)
95 free(httpd
->connections
);
101 httpd_add_connection(httpd_t
*httpd
, int fd
, unsigned char *local
, int local_len
, unsigned char *remote
, int remote_len
)
105 for (i
=0; i
<httpd
->max_connections
; i
++) {
106 if (!httpd
->connections
[i
].connected
) {
110 if (i
== httpd
->max_connections
) {
111 /* This code should never be reached, we do not select server_fds when full */
112 logger_log(httpd
->logger
, LOGGER_INFO
, "Max connections reached");
113 shutdown(fd
, SHUT_RDWR
);
118 httpd
->open_connections
++;
119 httpd
->connections
[i
].socket_fd
= fd
;
120 httpd
->connections
[i
].connected
= 1;
121 httpd
->connections
[i
].user_data
= httpd
->callbacks
.conn_init(httpd
->callbacks
.opaque
, local
, local_len
, remote
, remote_len
);
125 httpd_accept_connection(httpd_t
*httpd
, int server_fd
, int is_ipv6
)
127 struct sockaddr_storage remote_saddr
;
128 socklen_t remote_saddrlen
;
129 struct sockaddr_storage local_saddr
;
130 socklen_t local_saddrlen
;
131 unsigned char *local
, *remote
;
132 int local_len
, remote_len
;
135 remote_saddrlen
= sizeof(remote_saddr
);
136 fd
= accept(server_fd
, (struct sockaddr
*)&remote_saddr
, &remote_saddrlen
);
138 /* FIXME: Error happened */
142 local_saddrlen
= sizeof(local_saddr
);
143 ret
= getsockname(fd
, (struct sockaddr
*)&local_saddr
, &local_saddrlen
);
149 logger_log(httpd
->logger
, LOGGER_INFO
, "Accepted %s client on socket %d",
150 (is_ipv6
? "IPv6" : "IPv4"), fd
);
151 local
= netutils_get_address(&local_saddr
, &local_len
);
152 remote
= netutils_get_address(&remote_saddr
, &remote_len
);
154 httpd_add_connection(httpd
, fd
, local
, local_len
, remote
, remote_len
);
159 httpd_remove_connection(httpd_t
*httpd
, http_connection_t
*connection
)
161 if (connection
->request
) {
162 http_request_destroy(connection
->request
);
163 connection
->request
= NULL
;
165 httpd
->callbacks
.conn_destroy(connection
->user_data
);
166 shutdown(connection
->socket_fd
, SHUT_WR
);
167 closesocket(connection
->socket_fd
);
168 connection
->connected
= 0;
169 httpd
->open_connections
--;
173 httpd_thread(void *arg
)
175 httpd_t
*httpd
= arg
;
187 MUTEX_LOCK(httpd
->run_mutex
);
188 if (!httpd
->running
) {
189 MUTEX_UNLOCK(httpd
->run_mutex
);
192 MUTEX_UNLOCK(httpd
->run_mutex
);
194 /* Set timeout value to 5ms */
198 /* Get the correct nfds value and set rfds */
200 if (httpd
->open_connections
< httpd
->max_connections
) {
201 if (httpd
->server_fd4
!= -1) {
202 FD_SET(httpd
->server_fd4
, &rfds
);
203 if (nfds
<= httpd
->server_fd4
) {
204 nfds
= httpd
->server_fd4
+1;
207 if (httpd
->server_fd6
!= -1) {
208 FD_SET(httpd
->server_fd6
, &rfds
);
209 if (nfds
<= httpd
->server_fd6
) {
210 nfds
= httpd
->server_fd6
+1;
214 for (i
=0; i
<httpd
->max_connections
; i
++) {
216 if (!httpd
->connections
[i
].connected
) {
219 socket_fd
= httpd
->connections
[i
].socket_fd
;
220 FD_SET(socket_fd
, &rfds
);
221 if (nfds
<= socket_fd
) {
226 ret
= select(nfds
, &rfds
, NULL
, NULL
, &tv
);
228 /* Timeout happened */
230 } else if (ret
== -1) {
231 /* FIXME: Error happened */
232 logger_log(httpd
->logger
, LOGGER_INFO
, "Error in select");
236 if (httpd
->open_connections
< httpd
->max_connections
&&
237 httpd
->server_fd4
!= -1 && FD_ISSET(httpd
->server_fd4
, &rfds
)) {
238 ret
= httpd_accept_connection(httpd
, httpd
->server_fd4
, 0);
241 } else if (ret
== 0) {
245 if (httpd
->open_connections
< httpd
->max_connections
&&
246 httpd
->server_fd6
!= -1 && FD_ISSET(httpd
->server_fd6
, &rfds
)) {
247 ret
= httpd_accept_connection(httpd
, httpd
->server_fd6
, 1);
250 } else if (ret
== 0) {
254 for (i
=0; i
<httpd
->max_connections
; i
++) {
255 http_connection_t
*connection
= &httpd
->connections
[i
];
257 if (!connection
->connected
) {
260 if (!FD_ISSET(connection
->socket_fd
, &rfds
)) {
264 /* If not in the middle of request, allocate one */
265 if (!connection
->request
) {
266 connection
->request
= http_request_init();
267 assert(connection
->request
);
270 logger_log(httpd
->logger
, LOGGER_DEBUG
, "Receiving on socket %d", connection
->socket_fd
);
271 ret
= recv(connection
->socket_fd
, buffer
, sizeof(buffer
), 0);
273 logger_log(httpd
->logger
, LOGGER_INFO
, "Connection closed for socket %d", connection
->socket_fd
);
274 httpd_remove_connection(httpd
, connection
);
278 /* Parse HTTP request from data read from connection */
279 http_request_add_data(connection
->request
, buffer
, ret
);
280 if (http_request_has_error(connection
->request
)) {
281 logger_log(httpd
->logger
, LOGGER_INFO
, "Error in parsing: %s", http_request_get_error_name(connection
->request
));
282 httpd_remove_connection(httpd
, connection
);
286 /* If request is finished, process and deallocate */
287 if (http_request_is_complete(connection
->request
)) {
288 http_response_t
*response
= NULL
;
290 httpd
->callbacks
.conn_request(connection
->user_data
, connection
->request
, &response
);
291 http_request_destroy(connection
->request
);
292 connection
->request
= NULL
;
300 /* Get response data and datalen */
301 data
= http_response_get_data(response
, &datalen
);
304 while (written
< datalen
) {
305 ret
= send(connection
->socket_fd
, data
+written
, datalen
-written
, 0);
307 /* FIXME: Error happened */
308 logger_log(httpd
->logger
, LOGGER_INFO
, "Error in sending data");
314 if (http_response_get_disconnect(response
)) {
315 logger_log(httpd
->logger
, LOGGER_INFO
, "Disconnecting on software request");
316 httpd_remove_connection(httpd
, connection
);
319 logger_log(httpd
->logger
, LOGGER_INFO
, "Didn't get response");
321 http_response_destroy(response
);
323 logger_log(httpd
->logger
, LOGGER_DEBUG
, "Request not complete, waiting for more data...");
328 /* Remove all connections that are still connected */
329 for (i
=0; i
<httpd
->max_connections
; i
++) {
330 http_connection_t
*connection
= &httpd
->connections
[i
];
332 if (!connection
->connected
) {
335 logger_log(httpd
->logger
, LOGGER_INFO
, "Removing connection for socket %d", connection
->socket_fd
);
336 httpd_remove_connection(httpd
, connection
);
339 /* Close server sockets since they are not used any more */
340 if (httpd
->server_fd4
!= -1) {
341 closesocket(httpd
->server_fd4
);
342 httpd
->server_fd4
= -1;
344 if (httpd
->server_fd6
!= -1) {
345 closesocket(httpd
->server_fd6
);
346 httpd
->server_fd6
= -1;
349 logger_log(httpd
->logger
, LOGGER_INFO
, "Exiting HTTP thread");
355 httpd_start(httpd_t
*httpd
, unsigned short *port
)
357 /* How many connection attempts are kept in queue */
363 MUTEX_LOCK(httpd
->run_mutex
);
364 if (httpd
->running
|| !httpd
->joined
) {
365 MUTEX_UNLOCK(httpd
->run_mutex
);
369 httpd
->server_fd4
= netutils_init_socket(port
, 0, 0);
370 if (httpd
->server_fd4
== -1) {
371 logger_log(httpd
->logger
, LOGGER_ERR
, "Error initialising socket %d", SOCKET_GET_ERROR());
372 MUTEX_UNLOCK(httpd
->run_mutex
);
375 httpd
->server_fd6
= netutils_init_socket(port
, 1, 0);
376 if (httpd
->server_fd6
== -1) {
377 logger_log(httpd
->logger
, LOGGER_WARNING
, "Error initialising IPv6 socket %d", SOCKET_GET_ERROR());
378 logger_log(httpd
->logger
, LOGGER_WARNING
, "Continuing without IPv6 support");
381 if (httpd
->server_fd4
!= -1 && listen(httpd
->server_fd4
, backlog
) == -1) {
382 logger_log(httpd
->logger
, LOGGER_ERR
, "Error listening to IPv4 socket");
383 closesocket(httpd
->server_fd4
);
384 closesocket(httpd
->server_fd6
);
385 MUTEX_UNLOCK(httpd
->run_mutex
);
388 if (httpd
->server_fd6
!= -1 && listen(httpd
->server_fd6
, backlog
) == -1) {
389 logger_log(httpd
->logger
, LOGGER_ERR
, "Error listening to IPv6 socket");
390 closesocket(httpd
->server_fd4
);
391 closesocket(httpd
->server_fd6
);
392 MUTEX_UNLOCK(httpd
->run_mutex
);
395 logger_log(httpd
->logger
, LOGGER_INFO
, "Initialized server socket(s)");
397 /* Set values correctly and create new thread */
400 THREAD_CREATE(httpd
->thread
, httpd_thread
, httpd
);
401 MUTEX_UNLOCK(httpd
->run_mutex
);
407 httpd_is_running(httpd_t
*httpd
)
413 MUTEX_LOCK(httpd
->run_mutex
);
414 running
= httpd
->running
|| !httpd
->joined
;
415 MUTEX_UNLOCK(httpd
->run_mutex
);
421 httpd_stop(httpd_t
*httpd
)
425 MUTEX_LOCK(httpd
->run_mutex
);
426 if (!httpd
->running
|| httpd
->joined
) {
427 MUTEX_UNLOCK(httpd
->run_mutex
);
431 MUTEX_UNLOCK(httpd
->run_mutex
);
433 THREAD_JOIN(httpd
->thread
);
435 MUTEX_LOCK(httpd
->run_mutex
);
437 MUTEX_UNLOCK(httpd
->run_mutex
);