mirror of
https://github.com/oopuuu/zTC1.git
synced 2025-12-16 15:08:15 +08:00
662 lines
18 KiB
C
662 lines
18 KiB
C
/**
|
|
******************************************************************************
|
|
* @file httpd.c
|
|
* @author QQ DING
|
|
* @version V1.0.0
|
|
* @date 1-September-2015
|
|
* @brief The main HTTPD server thread and its initialization.
|
|
******************************************************************************
|
|
*
|
|
* The MIT License
|
|
* Copyright (c) 2014 MXCHIP Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is furnished
|
|
* to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "httpd.h"
|
|
#include "http-strings.h"
|
|
#include "httpd_priv.h"
|
|
#include "mico.h"
|
|
#include "SocketUtils.h"
|
|
#include "base64.h"
|
|
|
|
typedef enum
|
|
{
|
|
HTTPD_INACTIVE = 0,
|
|
HTTPD_INIT_DONE,
|
|
HTTPD_THREAD_RUNNING,
|
|
HTTPD_THREAD_SUSPENDED,
|
|
} httpd_state_t;
|
|
|
|
httpd_state_t httpd_state;
|
|
|
|
static mico_thread_t httpd_main_thread;
|
|
|
|
#define http_server_thread_stack_size 0x2000
|
|
|
|
/* Why HTTPD_MAX_MESSAGE + 2?
|
|
* Handlers are allowed to use HTTPD_MAX_MESSAGE bytes of this buffer.
|
|
* Internally, the POST var processing needs a null termination byte and an
|
|
* '&' termination byte.
|
|
*/
|
|
static bool httpd_stop_req;
|
|
|
|
#define HTTPD_CLIENT_SOCK_TIMEOUT 10
|
|
#define HTTPD_TIMEOUT_EVENT 0
|
|
|
|
/** Maximum number of backlogged http connections
|
|
*
|
|
* httpd has a single listening socket from which it accepts connections.
|
|
* HTTPD_MAX_BACKLOG_CONN is the maximum number of connections that can be
|
|
* pending. For example, suppose a webpage contains 10 images. If a client
|
|
* attempts to load all 10 of those images at once, only the first
|
|
* HTTPD_MAX_BACKLOG_CONN attempts can succeed. Some clients will retry when
|
|
* the attempts fail; others will limit the maximum number of open connections
|
|
* that it has. But some may attempt to load all 10 simultaneously. If your
|
|
* web pages have many images, or css files, or java script files, you may
|
|
* need to increase this number.
|
|
*
|
|
* \note Your underlying TCP/IP stack may have other limitations
|
|
* besides the backlog. For example, the treck stack limits the
|
|
* number of system-wide TCP sockets to TM_OPTION_TCP_SOCKETS_MAX.
|
|
* You will have to adjust this value if you need more than
|
|
* TM_OPTION_TCP_SOCKETS_MAX simultaneous TCP sockets.
|
|
*
|
|
*/
|
|
#define HTTPD_MAX_BACKLOG_CONN 5
|
|
|
|
static int http_sockfd;
|
|
|
|
int client_sockfd;
|
|
static bool https_active;
|
|
|
|
bool httpd_is_https_active( )
|
|
{
|
|
return https_active;
|
|
}
|
|
|
|
static int net_get_sock_error( int sock )
|
|
{
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
static int httpd_close_sockets( )
|
|
{
|
|
int ret, status = kNoErr;
|
|
|
|
if ( http_sockfd != -1 )
|
|
{
|
|
ret = close( http_sockfd );
|
|
if ( ret != 0 )
|
|
{
|
|
httpd_d("failed to close http socket: %d", net_get_sock_error(http_sockfd));
|
|
status = -kInProgressErr;
|
|
}
|
|
http_sockfd = -1;
|
|
}
|
|
|
|
if ( client_sockfd != -1 )
|
|
{
|
|
ret = close( client_sockfd );
|
|
if ( ret != 0 )
|
|
{
|
|
httpd_d("Failed to close client socket: %d", net_get_sock_error(client_sockfd));
|
|
status = -kInProgressErr;
|
|
}
|
|
client_sockfd = -1;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static void httpd_suspend_thread( bool warn )
|
|
{
|
|
if ( warn )
|
|
{
|
|
httpd_d("Suspending thread");
|
|
} else
|
|
{
|
|
httpd_d("Suspending thread");
|
|
}
|
|
httpd_close_sockets( );
|
|
httpd_state = HTTPD_THREAD_SUSPENDED;
|
|
mico_rtos_suspend_thread( NULL );
|
|
}
|
|
|
|
static int httpd_setup_new_socket( int port )
|
|
{
|
|
int one = 1;
|
|
int status, sockfd;
|
|
struct sockaddr_in addr_listen;
|
|
|
|
/* create listening TCP socket */
|
|
sockfd = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
|
|
if ( sockfd < 0 )
|
|
{
|
|
status = net_get_sock_error( sockfd );
|
|
httpd_d("Socket creation failed: Port: %d Status: %d", port, status);
|
|
return status;
|
|
}
|
|
|
|
setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one) );
|
|
|
|
addr_listen.sin_family = AF_INET;
|
|
addr_listen.sin_addr.s_addr = INADDR_ANY;
|
|
addr_listen.sin_port = htons( port );
|
|
|
|
/* bind insocket */
|
|
status = bind( sockfd, (struct sockaddr *) &addr_listen, sizeof(addr_listen) );
|
|
if ( status < 0 )
|
|
{
|
|
status = net_get_sock_error( sockfd );
|
|
httpd_d("Failed to bind socket on port: %d Status: %d", status, port);
|
|
return status;
|
|
}
|
|
|
|
status = listen( sockfd, HTTPD_MAX_BACKLOG_CONN );
|
|
if ( status < 0 )
|
|
{
|
|
status = net_get_sock_error( sockfd );
|
|
httpd_d("Failed to listen on port %d: %d.", port, status);
|
|
return status;
|
|
}
|
|
|
|
httpd_d("Listening on port %d.", port);
|
|
return sockfd;
|
|
}
|
|
|
|
static int httpd_setup_main_sockets( )
|
|
{
|
|
http_sockfd = httpd_setup_new_socket( HTTP_PORT );
|
|
if ( http_sockfd < 0 )
|
|
{
|
|
/* Socket creation failed */
|
|
return http_sockfd;
|
|
}
|
|
|
|
return kNoErr;
|
|
}
|
|
|
|
static int httpd_select( int max_sock, const fd_set *readfds,
|
|
fd_set *active_readfds,
|
|
int timeout_secs )
|
|
{
|
|
int activefds_cnt;
|
|
struct timeval timeout;
|
|
|
|
fd_set local_readfds;
|
|
|
|
if ( timeout_secs >= 0 )
|
|
timeout.tv_sec = timeout_secs;
|
|
timeout.tv_usec = 0;
|
|
|
|
memcpy( &local_readfds, readfds, sizeof(fd_set) );
|
|
httpd_d("WAITING for activity");
|
|
|
|
activefds_cnt = select(max_sock + 1, &local_readfds, NULL, NULL, timeout_secs >= 0 ? &timeout : NULL);
|
|
if (activefds_cnt < 0) {
|
|
httpd_d("Select failed: %d", timeout_secs);
|
|
httpd_suspend_thread( true );
|
|
}
|
|
|
|
if ( httpd_stop_req )
|
|
{
|
|
httpd_d("HTTPD stop request received");
|
|
httpd_stop_req = FALSE;
|
|
httpd_suspend_thread( false );
|
|
}
|
|
|
|
if ( activefds_cnt )
|
|
{
|
|
/* Update users copy of fd_set only if he wants */
|
|
if ( active_readfds )
|
|
memcpy( active_readfds, &local_readfds, sizeof(fd_set) );
|
|
return activefds_cnt;
|
|
}
|
|
|
|
httpd_d("TIMEOUT");
|
|
|
|
return HTTPD_TIMEOUT_EVENT;
|
|
}
|
|
|
|
static int httpd_accept_client_socket( const fd_set *active_readfds )
|
|
{
|
|
int main_sockfd = -1;
|
|
struct sockaddr addr_from;
|
|
socklen_t addr_from_len;
|
|
|
|
if ( FD_ISSET( http_sockfd, active_readfds ) )
|
|
{
|
|
main_sockfd = http_sockfd;
|
|
https_active = FALSE;
|
|
}
|
|
|
|
addr_from_len = sizeof(addr_from);
|
|
|
|
client_sockfd = accept( main_sockfd, &addr_from, &addr_from_len );
|
|
if ( client_sockfd < 0 )
|
|
{
|
|
httpd_d("net_accept client socket failed %d.", client_sockfd);
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
/*
|
|
* Enable TCP Keep-alive for accepted client connection
|
|
* -- By enabling this feature TCP sends probe packet if there is
|
|
* inactivity over connection for specfied interval
|
|
* -- If there is no response to probe packet for specified retries
|
|
* then connection is closed with RST packet to peer end
|
|
* -- Ref: http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/
|
|
*
|
|
* We are doing this as we have single threaded web server with
|
|
* synchronous (blocking) API usage like send, recv and they might get
|
|
* blocked due to un-availability of peer end, causing web server to
|
|
* be in-responsive forever.
|
|
*/
|
|
int optval = true;
|
|
if ( setsockopt( client_sockfd, SOL_SOCKET, 0x0008, &optval, sizeof(optval) ) == -1 )
|
|
{
|
|
httpd_d("Unsupported option SO_KEEPALIVE: %d", net_get_sock_error(client_sockfd));
|
|
}
|
|
|
|
/* TCP Keep-alive idle/inactivity timeout is 10 seconds */
|
|
optval = 10;
|
|
if ( setsockopt( client_sockfd, IPPROTO_TCP, 0x03, &optval, sizeof(optval) ) == -1 )
|
|
{
|
|
httpd_d("Unsupported option TCP_KEEPIDLE: %d", net_get_sock_error(client_sockfd));
|
|
}
|
|
|
|
/* TCP Keep-alive retry count is 5 */
|
|
optval = 5;
|
|
if ( setsockopt( client_sockfd, IPPROTO_TCP, 0x05, &optval, sizeof(optval) ) == -1 )
|
|
{
|
|
httpd_d("Unsupported option TCP_KEEPCNT: %d", net_get_sock_error(client_sockfd));
|
|
}
|
|
|
|
/* TCP Keep-alive retry interval (in case no response for probe
|
|
* packet) is 1 second.
|
|
*/
|
|
optval = 1;
|
|
if ( setsockopt( client_sockfd, IPPROTO_TCP, 0x04, &optval, sizeof(optval) ) == -1 )
|
|
{
|
|
httpd_d("Unsupported option TCP_KEEPINTVL: %d", net_get_sock_error(client_sockfd));
|
|
}
|
|
|
|
httpd_d("connecting %d to %d.", client_sockfd, addr_from.s_port);
|
|
|
|
return kNoErr;
|
|
}
|
|
|
|
static void httpd_handle_client_connection( const fd_set *active_readfds )
|
|
{
|
|
int activefds_cnt, status;
|
|
fd_set readfds;
|
|
|
|
if ( httpd_stop_req )
|
|
{
|
|
httpd_d("HTTPD stop request received");
|
|
httpd_stop_req = FALSE;
|
|
httpd_suspend_thread( false );
|
|
}
|
|
|
|
status = httpd_accept_client_socket( active_readfds );
|
|
if ( status != kNoErr )
|
|
return;
|
|
|
|
httpd_d("Client socket accepted: %d", client_sockfd);
|
|
FD_ZERO( &readfds );
|
|
FD_SET( client_sockfd, &readfds );
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( httpd_stop_req )
|
|
{
|
|
httpd_d("HTTPD stop request received");
|
|
httpd_stop_req = FALSE;
|
|
httpd_suspend_thread( false );
|
|
}
|
|
|
|
httpd_d("Waiting on client socket");
|
|
activefds_cnt = httpd_select( client_sockfd, &readfds, NULL, HTTPD_CLIENT_SOCK_TIMEOUT );
|
|
|
|
if ( httpd_stop_req )
|
|
{
|
|
httpd_d("HTTPD stop request received");
|
|
httpd_stop_req = FALSE;
|
|
httpd_suspend_thread( false );
|
|
}
|
|
|
|
if ( activefds_cnt == HTTPD_TIMEOUT_EVENT )
|
|
{
|
|
/* Timeout has occured */
|
|
httpd_d("Client socket timeout occurred. " "Force closing socket");
|
|
|
|
status = close( client_sockfd );
|
|
if ( status != kNoErr )
|
|
{
|
|
status = net_get_sock_error( client_sockfd );
|
|
httpd_d("Failed to close socket %d", status);
|
|
httpd_suspend_thread( true );
|
|
}
|
|
|
|
client_sockfd = -1;
|
|
break;
|
|
}
|
|
|
|
httpd_d("Handling %d", client_sockfd);
|
|
/* Note:
|
|
* Connection will be handled with call to
|
|
* httpd_handle_message twice, first for
|
|
* handling request (kNoErr) and second
|
|
* time as there is no more data to receive
|
|
* (client closed connection) and hence
|
|
* will return with status HTTPD_DONE
|
|
* closing socket.
|
|
*/
|
|
/* FIXME: remove this memset if all is working well */
|
|
/* memset(&httpd_message_in[0], 0, sizeof(httpd_message_in)); */
|
|
status = httpd_handle_message( client_sockfd );
|
|
if ( status == kNoErr )
|
|
{
|
|
/* The handlers are expected more data on the
|
|
socket */
|
|
continue;
|
|
}
|
|
|
|
/* Either there was some error or everything went well */
|
|
httpd_d("Close socket %d. %s: %d", client_sockfd, status == HTTPD_DONE ? "Handler done" : "Handler failed", status);
|
|
|
|
status = close( client_sockfd );
|
|
if ( status != kNoErr )
|
|
{
|
|
status = net_get_sock_error( client_sockfd );
|
|
httpd_d("Failed to close socket %d", status);
|
|
httpd_suspend_thread( true );
|
|
}
|
|
client_sockfd = -1;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void httpd_main( mico_thread_arg_t arg )
|
|
{
|
|
UNUSED_PARAMETER( arg );
|
|
int status, max_sockfd = -1;
|
|
fd_set readfds, active_readfds;
|
|
|
|
status = httpd_setup_main_sockets( );
|
|
if ( status != kNoErr )
|
|
httpd_suspend_thread( true );
|
|
|
|
FD_ZERO( &readfds );
|
|
FD_SET( http_sockfd, &readfds );
|
|
max_sockfd = http_sockfd;
|
|
|
|
while ( 1 )
|
|
{
|
|
httpd_d("Waiting on main socket");
|
|
httpd_select( max_sockfd, &readfds, &active_readfds, -1 );
|
|
httpd_handle_client_connection( &active_readfds );
|
|
}
|
|
|
|
/*
|
|
* Thread will never come here. The functions called from the above
|
|
* infinite loop will cleanly shutdown this thread when situation
|
|
* demands so.
|
|
*/
|
|
}
|
|
|
|
static inline int tcp_local_connect( int *sockfd )
|
|
{
|
|
uint16_t port;
|
|
int retry_cnt = 3;
|
|
|
|
httpd_d("Doing local connect for shutting down server\n\r");
|
|
|
|
*sockfd = -1;
|
|
while ( retry_cnt-- )
|
|
{
|
|
*sockfd = socket( AF_INET, SOCK_STREAM, 0 );
|
|
if ( *sockfd >= 0 )
|
|
break;
|
|
/* Wait some time to allow some sockets to get released */
|
|
mico_thread_msleep( 1000 );
|
|
}
|
|
|
|
if ( *sockfd < 0 )
|
|
{
|
|
httpd_d("Unable to create socket to stop server");
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
port = HTTP_PORT;
|
|
|
|
char *host = "127.0.0.1";
|
|
struct sockaddr_in addr;
|
|
memset( &addr, 0, sizeof(struct sockaddr_in) );
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = inet_addr( host );
|
|
addr.sin_port = htons( port );
|
|
|
|
httpd_d("local connecting ...");
|
|
if ( connect( *sockfd, (struct sockaddr *) &addr, sizeof(addr) ) != 0 )
|
|
{
|
|
httpd_d("Server close error. tcp connect failed %s:%d", host, port);
|
|
close( *sockfd );
|
|
*sockfd = 0;
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
/*
|
|
* We do not wish to do anything with this connection. Its sole
|
|
* purpose was to wake the main httpd thread out of sleep.
|
|
*/
|
|
|
|
return kNoErr;
|
|
}
|
|
|
|
static int httpd_signal_and_wait_for_halt( )
|
|
{
|
|
const int total_wait_time_ms = 1000 * 20; /* 20 seconds */
|
|
const int check_interval_ms = 100; /* 100 ms */
|
|
|
|
int num_iterations = total_wait_time_ms / check_interval_ms;
|
|
|
|
httpd_d("Sent stop request");
|
|
httpd_stop_req = TRUE;
|
|
|
|
/* Do a dummy local connect to wakeup the httpd thread */
|
|
int sockfd;
|
|
int rv = tcp_local_connect( &sockfd );
|
|
if ( rv != kNoErr )
|
|
return rv;
|
|
|
|
while ( httpd_state != HTTPD_THREAD_SUSPENDED && num_iterations-- )
|
|
{
|
|
mico_thread_msleep( check_interval_ms );
|
|
}
|
|
|
|
close( sockfd );
|
|
if ( httpd_state == HTTPD_THREAD_SUSPENDED )
|
|
return kNoErr;
|
|
|
|
httpd_d("Timed out waiting for httpd to stop. " "Force closed temporary socket");
|
|
|
|
httpd_stop_req = FALSE;
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
static int httpd_thread_cleanup( void )
|
|
{
|
|
int status = kNoErr;
|
|
|
|
switch ( httpd_state )
|
|
{
|
|
case HTTPD_INIT_DONE:
|
|
/*
|
|
* We have no threads, no sockets to close.
|
|
*/
|
|
break;
|
|
case HTTPD_THREAD_RUNNING:
|
|
status = httpd_signal_and_wait_for_halt( );
|
|
if ( status != kNoErr )
|
|
httpd_d("Unable to stop thread. Force killing it.");
|
|
/* No break here on purpose */
|
|
case HTTPD_THREAD_SUSPENDED:
|
|
status = mico_rtos_delete_thread( &httpd_main_thread );
|
|
if ( status != kNoErr )
|
|
httpd_d("Failed to delete thread.");
|
|
status = httpd_close_sockets( );
|
|
httpd_state = HTTPD_INIT_DONE;
|
|
break;
|
|
default:
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int httpd_is_running( void )
|
|
{
|
|
return (httpd_state == HTTPD_THREAD_RUNNING);
|
|
}
|
|
|
|
/* This pairs with httpd_stop() */
|
|
int httpd_start( void )
|
|
{
|
|
int status;
|
|
|
|
if ( httpd_state != HTTPD_INIT_DONE )
|
|
{
|
|
httpd_d("Already started");
|
|
return kNoErr;
|
|
}
|
|
|
|
status = mico_rtos_create_thread( &httpd_main_thread, MICO_APPLICATION_PRIORITY, "httpd",
|
|
httpd_main,
|
|
http_server_thread_stack_size, 0 );
|
|
|
|
if ( status != kNoErr )
|
|
{
|
|
httpd_d("Failed to create httpd thread: %d", status);
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
httpd_state = HTTPD_THREAD_RUNNING;
|
|
return kNoErr;
|
|
}
|
|
|
|
/* This pairs with httpd_start() */
|
|
int httpd_stop( void )
|
|
{
|
|
return httpd_thread_cleanup( );
|
|
}
|
|
|
|
/* This pairs with httpd_init() */
|
|
int httpd_shutdown( void )
|
|
{
|
|
int ret;
|
|
|
|
httpd_d("Shutting down.");
|
|
|
|
ret = httpd_thread_cleanup( );
|
|
if ( ret != kNoErr )
|
|
httpd_d("Thread cleanup failed");
|
|
|
|
httpd_state = HTTPD_INACTIVE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* This pairs with httpd_shutdown() */
|
|
int httpd_init( )
|
|
{
|
|
int status;
|
|
|
|
if ( httpd_state != HTTPD_INACTIVE )
|
|
return kNoErr;
|
|
|
|
httpd_d("Initializing");
|
|
|
|
client_sockfd = -1;
|
|
http_sockfd = -1;
|
|
|
|
status = httpd_wsgi_init( );
|
|
if ( status != kNoErr )
|
|
{
|
|
httpd_d("Failed to initialize WSGI!");
|
|
return status;
|
|
}
|
|
|
|
status = httpd_ssi_init( );
|
|
if ( status != kNoErr )
|
|
{
|
|
httpd_d("Failed to initialize SSI!");
|
|
return status;
|
|
}
|
|
|
|
httpd_state = HTTPD_INIT_DONE;
|
|
|
|
return kNoErr;
|
|
}
|
|
|
|
int httpd_use_tls_certificates( const httpd_tls_certs_t *tls_certs )
|
|
{
|
|
|
|
httpd_d("HTTPS is not enabled in server. ");
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
static char *auth_str = NULL;
|
|
|
|
int httpd_auth_init(char *name, char *passwd)
|
|
{
|
|
int len, outlen;
|
|
char *src_str;
|
|
|
|
len = strlen(name) + strlen(passwd) + 2;
|
|
|
|
if (auth_str)
|
|
free(auth_str);
|
|
|
|
auth_str = NULL;
|
|
if (strlen(name) == 0 && strlen(passwd) == 0) // no username and password
|
|
return 0;
|
|
|
|
src_str = malloc(len);
|
|
if (src_str == 0)
|
|
return -1;
|
|
|
|
sprintf(src_str, "%s:%s", name, passwd);
|
|
auth_str = (char *)base64_encode((unsigned char const *)src_str, strlen(src_str), &outlen);
|
|
len = strlen(auth_str);
|
|
auth_str[len-1] = 0;
|
|
free(src_str);
|
|
return kNoErr;
|
|
}
|
|
|
|
char *get_httpd_auth( void )
|
|
{
|
|
return auth_str;
|
|
}
|