//============================================================================= // YHTTPD // Webserver Class : Until now: exact one instance //============================================================================= // c++ #include #include // system #include #include #include #include #include #include #include #include // tuxbox #include // yhttpd #include #include "ywebserver.h" #include "ylogging.h" #include "helper.h" #include "yconnection.h" #include "yrequest.h" #include //============================================================================= // Initialization of static variables //============================================================================= bool CWebserver::is_threading = true; pthread_mutex_t CWebserver::mutex = PTHREAD_MUTEX_INITIALIZER; ; //============================================================================= // Constructor & Destructor & Initialization //============================================================================= CWebserver::CWebserver() { terminate = false; for (int i = 0; i < HTTPD_MAX_CONNECTIONS; i++) { Connection_Thread_List[i] = (pthread_t) NULL; SocketList[i] = NULL; } FD_ZERO(&master); // initialize FD_SETs FD_ZERO(&read_fds); fdmax = 0; open_connections = 0; #ifdef Y_CONFIG_FEATURE_THREADING pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); #endif port = HTTPD_STANDARD_PORT; } //----------------------------------------------------------------------------- CWebserver::~CWebserver() { listenSocket.close(); } //============================================================================= // Start Webserver. Main-Loop. //----------------------------------------------------------------------------- // Wait for Connection and schedule ist to handle_connection() // HTTP/1.1 should can handle "keep-alive" connections to reduce socket // creation and handling. This is handled using FD_SET and select Socket // mechanism. // select wait for socket-activity. Cases: // 1) get a new connection // 2) re-use a socket // 3) timeout: close unused sockets //----------------------------------------------------------------------------- // from RFC 2616: // 8 Connections // 8.1 Persistent Connections // 8.1.1 Purpose // // Prior to persistent connections, a separate TCP connection was // established to fetch each URL, increasing the load on HTTP servers // and causing congestion on the Internet. The use of inline images and // other associated data often require a client to make multiple // requests of the same server in a short amount of time. Analysis of // these performance problems and results from a prototype // implementation are available [26] [30]. Implementation experience and // measurements of actual HTTP/1.1 (RFC 2068) implementations show good // results [39]. Alternatives have also been explored, for example, // T/TCP [27]. // // Persistent HTTP connections have a number of advantages: // // - By opening and closing fewer TCP connections, CPU time is saved // in routers and hosts (clients, servers, proxies, gateways, // tunnels, or caches), and memory used for TCP protocol control // blocks can be saved in hosts. // // - HTTP requests and responses can be pipelined on a connection. // Pipelining allows a client to make multiple requests without // waiting for each response, allowing a single TCP connection to // be used much more efficiently, with much lower elapsed time. // // - Network congestion is reduced by reducing the number of packets // caused by TCP opens, and by allowing TCP sufficient time to // determine the congestion state of the network. // // - Latency on subsequent requests is reduced since there is no time // spent in TCP's connection opening handshake. // // - HTTP can evolve more gracefully, since errors can be reported // without the penalty of closing the TCP connection. Clients using // future versions of HTTP might optimistically try a new feature, // but if communicating with an older server, retry with old // semantics after an error is reported. // // HTTP implementations SHOULD implement persistent connections. //============================================================================= #define MAX_TIMEOUTS_TO_CLOSE 10 #define MAX_TIMEOUTS_TO_TEST 100 bool CWebserver::run(void) { set_threadname("ywebsrv::run"); if (!listenSocket.listen(port, HTTPD_MAX_CONNECTIONS)) { if (port != HTTPD_STANDARD_PORT) { // WebsiteMain.port in nhttpd.conf is changed by user fprintf(stderr, "[yhttpd] Socket cannot bind and listen on port %d. Abort.\n", port); return false; } fprintf(stderr, "[yhttpd] Socket cannot bind and listen on port %d. Retrying port %d.\n", HTTPD_STANDARD_PORT, HTTPD_FALLBACK_PORT); port = HTTPD_FALLBACK_PORT; if (!listenSocket.listen(port, HTTPD_MAX_CONNECTIONS)) { fprintf(stderr, "[yhttpd] Socket cannot bind and listen on port %d. Abort.\n", HTTPD_FALLBACK_PORT); return false; } } #ifdef Y_CONFIG_FEATURE_KEEP_ALIVE // initialize values for select int listener = listenSocket.get_socket();// Open Listener struct timeval tv; // timeout struct FD_SET(listener, &master); // add the listener to the master set fdmax = listener; // init max fd fcntl(listener, F_SETFD , O_NONBLOCK); // listener master socket non-blocking int timeout_counter = 0; // Counter for Connection Timeout checking int test_counter = 0; // Counter for Testing long running Connections // main Webserver Loop while(!terminate) { // select : init vars read_fds = master; // copy it tv.tv_usec = 10000; // microsec: Timeout for select ! for re-use / keep-alive socket tv.tv_sec = 0; // seconds int fd = -1; // select : wait for socket activity if(open_connections <= 0) // No open Connection. Wait in select. fd = select(fdmax+1,&read_fds, NULL, NULL, NULL);// wait for socket activity else fd = select(fdmax+1,&read_fds, NULL, NULL, &tv);// wait for socket activity or timeout // too much to do : sleep if(open_connections >= HTTPD_MAX_CONNECTIONS-1) sleep(1); // Socket Error? if(fd == -1 && errno != EINTR) { perror("select"); return false; } // Socket Timeout? if(fd == 0) { // Testoutput for long living threads if(++test_counter >= MAX_TIMEOUTS_TO_TEST) { for(int j=0;j < HTTPD_MAX_CONNECTIONS;j++) if(SocketList[j] != NULL) // here is a socket log_level_printf(2,"FD-TEST sock:%d handle:%d open:%d\n",SocketList[j]->get_socket(), SocketList[j]->handling,SocketList[j]->isOpened); test_counter=0; } // some connection closing previous missed? if(++timeout_counter >= MAX_TIMEOUTS_TO_CLOSE) { CloseConnectionSocketsByTimeout(); timeout_counter=0; } continue; // main loop again } //---------------------------------------------------------------------------------------- // Check all observed descriptors & check new or re-use Connections //---------------------------------------------------------------------------------------- for(int i = listener; i <= fdmax; i++) { int slot = -1; if(FD_ISSET(i, &read_fds)) // Socket observed? { // we got one!! if (i == listener) // handle new connections slot = AcceptNewConnectionSocket(); else // Connection on an existing open Socket = reuse (keep-alive) { slot = SL_GetExistingSocket(i); if(slot>=0) log_level_printf(2,"FD: reuse con fd:%d\n",SocketList[slot]->get_socket()); } // prepare Connection handling if(slot>=0) if(SocketList[slot] != NULL && !SocketList[slot]->handling && SocketList[slot]->isValid) { log_level_printf(2,"FD: START CON HANDLING con fd:%d\n",SocketList[slot]->get_socket()); FD_CLR(SocketList[slot]->get_socket(), &master); // remove from master set SocketList[slot]->handling = true; // prepares for thread-handling if(!handle_connection(SocketList[slot]))// handle this activity { // Can not handle more threads char httpstr[]=HTTP_PROTOCOL " 503 Service Unavailable\r\n\r\n"; SocketList[slot]->Send(httpstr, strlen(httpstr)); SL_CloseSocketBySlot(slot); } } } }// for CloseConnectionSocketsByTimeout(); // Check connections to close }//while #else while (!terminate) { CySocket *newConnectionSock; if (!(newConnectionSock = listenSocket.accept())) //Now: Blocking wait { pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_testcancel(); dperror("Socket accept error. Continue.\n"); continue; } log_level_printf(3, "Socket connect from %s\n", (listenSocket.get_client_ip()).c_str()); #ifdef Y_CONFIG_USE_OPEN_SSL if(Cyhttpd::ConfigList["SSL"]=="true") newConnectionSock->initAsSSL(); // make it a SSL-socket #endif handle_connection(newConnectionSock); } #endif return true; } //============================================================================= // SocketList Handler //============================================================================= //----------------------------------------------------------------------------- // Accept new Connection //----------------------------------------------------------------------------- int CWebserver::AcceptNewConnectionSocket() { int slot = -1; CySocket *connectionSock = NULL; if (!(connectionSock = listenSocket.accept())) // Blocking wait { dperror("Socket accept error. Continue.\n"); delete connectionSock; return -1; } #ifdef Y_CONFIG_USE_OPEN_SSL if(Cyhttpd::ConfigList["SSL"]=="true") connectionSock->initAsSSL(); // make it a SSL-socket #endif log_level_printf(2, "FD: new con fd:%d on port:%d\n", connectionSock->get_socket(), connectionSock->get_accept_port()); // Add Socket to List slot = SL_GetFreeSlot(); if (slot < 0) { connectionSock->close(); aprintf("No free Slot in SocketList found. Open:%d\n", open_connections); } else { SocketList[slot] = connectionSock; // put it to list fcntl(connectionSock->get_socket(), F_SETFD, O_NONBLOCK); // set non-blocking open_connections++; // count open connectins int newfd = connectionSock->get_socket(); if (newfd > fdmax) // keep track of the maximum fd fdmax = newfd; } return slot; } //----------------------------------------------------------------------------- // Get Index for Socket from SocketList //----------------------------------------------------------------------------- int CWebserver::SL_GetExistingSocket(SOCKET sock) { int slot = -1; for (int j = 0; j < HTTPD_MAX_CONNECTIONS; j++) if (SocketList[j] != NULL // here is a socket && SocketList[j]->get_socket() == sock) // we know that socket { slot = j; break; } return slot; } //----------------------------------------------------------------------------- // Get Index for free Slot in SocketList //----------------------------------------------------------------------------- int CWebserver::SL_GetFreeSlot() { int slot = -1; for (int j = 0; j < HTTPD_MAX_CONNECTIONS; j++) if (SocketList[j] == NULL) // here is a free slot { slot = j; break; } return slot; } //----------------------------------------------------------------------------- // Look for Sockets to close //----------------------------------------------------------------------------- void CWebserver::CloseConnectionSocketsByTimeout() { CySocket *connectionSock = NULL; for (int j = 0; j < HTTPD_MAX_CONNECTIONS; j++) if (SocketList[j] != NULL // here is a socket && !SocketList[j]->handling) // it is not handled { connectionSock = SocketList[j]; SOCKET thisSocket = connectionSock->get_socket(); bool shouldClose = true; if (!connectionSock->isValid) // If not valid -> close ; // close else if (connectionSock->tv_start_waiting.tv_sec != 0 || SocketList[j]->tv_start_waiting.tv_usec != 0) { // calculate keep-alive timeout struct timeval tv_now; struct timezone tz_now; gettimeofday(&tv_now, &tz_now); int64_t tdiff = ((tv_now.tv_sec - connectionSock->tv_start_waiting.tv_sec) * 1000000 + (tv_now.tv_usec - connectionSock->tv_start_waiting.tv_usec)); if (tdiff < HTTPD_KEEPALIVE_TIMEOUT || tdiff < 0) shouldClose = false; } if (shouldClose) { log_level_printf(2, "FD: close con Timeout fd:%d\n", thisSocket); SL_CloseSocketBySlot(j); } } } //----------------------------------------------------------------------------- // Add Socket fd to FD_SET again (for select-handling) // Add start-time for waiting for connection re-use / keep-alive //----------------------------------------------------------------------------- void CWebserver::addSocketToMasterSet(SOCKET fd) { int slot = SL_GetExistingSocket(fd); // get slot/index for fd if (slot < 0) return; log_level_printf(2, "FD: add to master fd:%d\n", fd); struct timeval tv_now; struct timezone tz_now; gettimeofday(&tv_now, &tz_now); SocketList[slot]->tv_start_waiting = tv_now; // add keep-alive wait time FD_SET(fd, &master); // add fd to select-master-set } //----------------------------------------------------------------------------- // Close (FD_SET handled) Socket // Clear it from SocketList //----------------------------------------------------------------------------- void CWebserver::SL_CloseSocketBySlot(int slot) { open_connections--; // count open connections if (SocketList[slot] == NULL) return; SocketList[slot]->handling = false; // no handling anymore FD_CLR(SocketList[slot]->get_socket(), &master);// remove from master set SocketList[slot]->close(); // close the socket delete SocketList[slot]; // destroy ySocket SocketList[slot] = NULL; // free in list } //============================================================================= // Thread Handling //============================================================================= //----------------------------------------------------------------------------- // Check if IP is allowed for keep-alive //----------------------------------------------------------------------------- bool CWebserver::CheckKeepAliveAllowedByIP(std::string client_ip) { pthread_mutex_lock(&mutex); bool do_keep_alive = true; CStringVector::const_iterator it = conf_no_keep_alive_ips.begin(); while (it != conf_no_keep_alive_ips.end()) { if (trim(*it) == client_ip) do_keep_alive = false; ++it; } pthread_mutex_unlock(&mutex); return do_keep_alive; } //----------------------------------------------------------------------------- // Set Entry(number)to NULL in Threadlist //----------------------------------------------------------------------------- void CWebserver::clear_Thread_List_Number(int number) { pthread_mutex_lock(&mutex); if (number < HTTPD_MAX_CONNECTIONS) Connection_Thread_List[number] = (pthread_t) NULL; CloseConnectionSocketsByTimeout(); pthread_mutex_unlock(&mutex); } //----------------------------------------------------------------------------- // A new Connection is established to newSock. Create a (threaded) Connection // and handle the Request. //----------------------------------------------------------------------------- bool CWebserver::handle_connection(CySocket *newSock) { void *WebThread(void *args); //forward declaration // create arguments TWebserverConnectionArgs *newConn = new TWebserverConnectionArgs; if (!newConn) { dperror("CWebserver TWebserverConnectionArgs error!\n"); return false; } newConn->ySock = newSock; newConn->ySock->handling = true; newConn->WebserverBackref = this; #ifdef Y_CONFIG_FEATURE_THREADING newConn->is_treaded = is_threading; #else newConn->is_treaded = false; #endif int index = -1; #ifdef Y_CONFIG_FEATURE_THREADING if(is_threading) { pthread_mutex_lock( &mutex ); // look for free Thread slot for(int i=0;ithread_number = index; //remember Index of Thread slot (for clean up) // Create an orphan Thread. It is not joinable anymore pthread_mutex_unlock( &mutex ); // start connection Thread if(pthread_create(&Connection_Thread_List[index], &attr, WebThread, (void *)newConn) != 0) dperror("Could not create Connection-Thread\n"); } else // non threaded #endif WebThread((void *) newConn); return ((index != -1) || !is_threading); } //------------------------------------------------------------------------- // Webserver-Thread for each connection //------------------------------------------------------------------------- void *WebThread(void *args) { TWebserverConnectionArgs *newConn = (TWebserverConnectionArgs *) args; if (!newConn) { dperror("WebThread called without arguments!\n"); return NULL; } bool is_threaded = newConn->is_treaded; if (is_threaded) log_level_printf(1, "++ Thread 0x06%X gestartet\n", (int) pthread_self()); // (1) create & init Connection CWebserver *ws = newConn->WebserverBackref; if (!ws) { dperror("WebThread CWebserver error!\n"); return NULL; } CWebserverConnection *con = new CWebserverConnection(ws); if (!con) { dperror("WebThread CWebserverConnection error!\n"); return NULL; } con->Request.UrlData["clientaddr"] = newConn->ySock->get_client_ip(); // TODO:here? con->sock = newConn->ySock; // give socket reference newConn->ySock->handling = true; // dont handle this socket now be webserver main loop // (2) handle the connection con->HandleConnection(); // (3) end connection handling #ifdef Y_CONFIG_FEATURE_KEEP_ALIVE if(!con->keep_alive) log_level_printf(2,"FD SHOULD CLOSE sock:%d!!!\n",con->sock->get_socket()); else ws->addSocketToMasterSet(con->sock->get_socket()); // add to master set #endif if (!con->keep_alive) con->sock->isValid = false; con->sock->handling = false; // socket can be handled by webserver main loop (select) again #ifndef Y_CONFIG_FEATURE_KEEP_ALIVE delete newConn->ySock; newConn->ySock = NULL; #endif // (4) end thread delete con; int thread_number = newConn->thread_number; delete newConn; if (is_threaded) { log_level_printf(1, "-- Thread 0x06%X beendet\n", (int) pthread_self()); ws->clear_Thread_List_Number(thread_number); pthread_exit( NULL); } return NULL; }