EHS::StartServerResult EHS::StartServer ( EHSServerParameters & iroEHSServerParameters ) { StartServerResult nResult = STARTSERVER_INVALID; m_oEHSServerParameters = iroEHSServerParameters; if ( poEHSServer != NULL ) { EHS_TRACE ( "Warning: Tried to start server that was already running\n" ); nResult = STARTSERVER_ALREADYRUNNING; } else { // associate a EHSServer object to this EHS object poEHSServer = new EHSServer ( this ); if ( poEHSServer->m_nServerRunningStatus == EHSServer::SERVERRUNNING_NOTRUNNING ) { EHS_TRACE ( "Error: Failed to start server\n" ); return STARTSERVER_FAILED; } } return STARTSERVER_SUCCESS; }
HttpResponse * EHS::RouteRequest ( HttpRequest * ipoHttpRequest ///< request info for service ) { // get the next path from the URI std::string sNextPathPart = GetNextPathPart ( ipoHttpRequest->sUri ); EHS_TRACE ( "Info: Trying to route: '%s'\n", sNextPathPart.c_str ( ) ); // if there is no more path, call HandleRequest on this EHS object with // whatever's left - or if we're not routing if ( sNextPathPart.empty ( ) || m_oEHSServerParameters.find ( "norouterequest" ) != m_oEHSServerParameters.end ( ) ) { // create an HttpRespose object for the client HttpResponse * poHttpResponse = new HttpResponse ( ipoHttpRequest->m_nRequestId, ipoHttpRequest->m_poSourceEHSConnection ); // get the actual response and return code poHttpResponse->m_nResponseCode = HandleRequest ( ipoHttpRequest, poHttpResponse ); return poHttpResponse; } // if the path exists, check it against the map of EHSs if ( oEHSMap [ sNextPathPart ] ) { // if it exists, call RouteRequest with that EHS and the // new shortened path return oEHSMap [ sNextPathPart ]->RouteRequest ( ipoHttpRequest ); } // if it doesn't exist, send an error back up saying resource doesn't exist else { EHS_TRACE ( "Info: Routing failed. Most likely caused by an invalid URL, not internal error\n" ); // send back a 404 response HttpResponse * poHttpResponse = new HttpResponse ( ipoHttpRequest->m_nRequestId, ipoHttpRequest->m_poSourceEHSConnection ); poHttpResponse->m_nResponseCode = HTTPRESPONSECODE_404_NOTFOUND; poHttpResponse->SetBody ( "404 - Not Found", strlen ( "404 - Not Found" ) ); return poHttpResponse; } }
void EHSConnection::AddResponse ( HttpResponse * ipoHttpResponse ) { MUTEX_LOCK ( m_oConnectionMutex ); // push the object on to the list m_oHttpResponseMap [ ipoHttpResponse->m_nResponseId ] = ipoHttpResponse; // go through the list until we can't find the next response to send int nFoundNextResponse = 0; do { nFoundNextResponse = 0; if ( m_oHttpResponseMap.find ( m_nResponses + 1 ) != m_oHttpResponseMap.end ( ) ) { nFoundNextResponse = 1; HttpResponseMap::iterator i = m_oHttpResponseMap.find ( m_nResponses + 1 ); SendHttpResponse ( i->second ); delete i->second; m_oHttpResponseMap.erase ( i ); m_nResponses++; // set last activity to the current time for idle purposes UpdateLastActivity ( ); // if we're done with this connection, get rid of it if ( CheckDone ( ) ) { EHS_TRACE( "add response found something to delete\n" ); // careful with mutexes around here.. Don't want to hold both MUTEX_UNLOCK ( m_oConnectionMutex ); MUTEX_LOCK ( m_poEHSServer->m_oMutex ); m_poEHSServer->RemoveEHSConnection ( this ); MUTEX_UNLOCK ( m_poEHSServer->m_oMutex ); return; } EHS_TRACE ( "Sending response %d to %x\n", m_nResponses, this ); } } while ( nFoundNextResponse == 1 ); MUTEX_UNLOCK ( m_oConnectionMutex ); }
int EHSServer::RemoveEHSConnection ( EHSConnection * ipoEHSConnection ) { // don't lock as it's only called from within locked sections assert ( ipoEHSConnection != NULL ); int nDeletedOneAlready = 0; EHS_TRACE ( "%d elements to look for somethign to delete\n", m_oEHSConnectionList.size ( ) ); // go through the list and find all occurances of ipoEHSConnection for ( EHSConnectionList::iterator i = m_oEHSConnectionList.begin ( ); i != m_oEHSConnectionList.end ( ); /* no third part */ ) { if ( *i == ipoEHSConnection ) { if ( nDeletedOneAlready ) { EHS_TRACE ( "FATAL ERROR: Deleting a second element in RemoveEHSConnection - EXITING\n" ); exit ( 2 ); } nDeletedOneAlready = 1; // close the FD EHSConnection * poEHSConnection = *i; NetworkAbstraction * poNetworkAbstraction = poEHSConnection->GetNetworkAbstraction ( ); poNetworkAbstraction->Close ( ); #if STOP_MEMORY_LEAKS // Needs testing delete poEHSConnection; #endif // start back over at the beginning of the list m_oEHSConnectionList.erase ( i ); i = m_oEHSConnectionList.begin ( ); // Mark as unused poEHSConnection->m_UnusedSyncId = m_ThreadsSyncPoint.GetSyncId (); m_oEHSConnectionUnusedList.push_back ( poEHSConnection ); } else { i++; } } return nDeletedOneAlready; }
int EHSServer::CreateFdSet ( ) { MUTEX_LOCK ( m_oMutex ); // don't lock mutex, as it is only called from within a locked section FD_ZERO( &m_oReadFds ); // add the accepting FD FD_SET( m_poNetworkAbstraction->GetFd ( ), &m_oReadFds ); int nHighestFd = m_poNetworkAbstraction->GetFd ( ); for ( EHSConnectionList::iterator oCurrentConnection = m_oEHSConnectionList.begin ( ); !( oCurrentConnection == m_oEHSConnectionList.end ( ) ); oCurrentConnection++ ) { /// skip this one if it's already been used if ( (*oCurrentConnection)->StillReading ( ) ) { int nCurrentFd = ( * oCurrentConnection )->GetNetworkAbstraction ( )->GetFd ( ); EHS_TRACE ( "Adding %d to FD SET\n", nCurrentFd ); FD_SET ( nCurrentFd, &m_oReadFds ); // store the highest FD in the set to return it if ( nCurrentFd > nHighestFd ) { nHighestFd = nCurrentFd; } } else { EHS_TRACE ( "FD %d isn't reading anymore\n", ( * oCurrentConnection )->GetNetworkAbstraction ( )->GetFd ( ) ); } } MUTEX_UNLOCK ( m_oMutex ); return nHighestFd; }
int EHSServer::ClearIdleConnections ( ) { // don't lock mutex, as it is only called rom within locked sections int nIdleConnections = 0; for ( EHSConnectionList::iterator i = m_oEHSConnectionList.begin ( ); i != m_oEHSConnectionList.end ( ); i++ ) { if ( MUTEX_TRY_LOCK ( (*i)->m_oConnectionMutex ) == false ) continue; // if it's been more than N seconds since a response has been // sent and there are no pending requests if ( (*i)->StillReading ( ) && ( time ( NULL ) - (*i)->LastActivity ( ) > m_nIdleTimeout || (*i)->m_iStopASAP ) && !(*i)->RequestsPending ( ) ) { EHS_TRACE ( "Done reading because of idle timeout\n" ); (*i)->DoneReading ( false ); nIdleConnections++; } MUTEX_UNLOCK ( (*i)->m_oConnectionMutex ); } if ( nIdleConnections > 0 ) { EHS_TRACE ( "Cleared %d connections\n", nIdleConnections ); } RemoveFinishedConnections ( ); return nIdleConnections; }
void EHSServer::HandleData ( int inTimeoutMilliseconds, ///< milliseconds for timeout on select pthread_t inThreadId ///< numeric ID for this thread to help debug ) { //fprintf ( stderr, "##### [%d] Trying to lock server mutex\n", inThreadId ); MUTEX_LOCK ( m_oMutex ); //fprintf ( stderr, "##### [%d] Got lock on server mutex\n", inThreadId ); // determine if there are any jobs waiting if this thread should -- // if we're running one-thread-per-request and this is the accept thread // we don't look for requests HttpRequest * poHttpRequest = NULL; if ( m_nServerRunningStatus != SERVERRUNNING_ONETHREADPERREQUEST || !pthread_equal(inThreadId,m_nAcceptThreadId) ) { poHttpRequest = GetNextRequest ( ); } // if we got a request to handle if ( poHttpRequest != NULL ) { //fprintf ( stderr, "##### [%d] Got a request to handle\n", inThreadId ); // handle the request and post it back to the connection object MUTEX_UNLOCK ( m_oMutex ); // route the request HttpResponse * poHttpResponse = m_poTopLevelEHS->RouteRequest ( poHttpRequest ); // add the response to the appropriate connection's response list poHttpResponse->m_poEHSConnection->AddResponse ( poHttpResponse ); delete poHttpRequest; } // otherwise, no requests are pending else { // if something is already accepting, sleep if ( m_nAccepting ) { // wait until something happens // it's ok to not recheck our condition here, as we'll come back in the same way and recheck then //fprintf ( stderr, "##### [%d] Sleeping because no requests and someone else is accepting\n", inThreadId ); pthread_cond_wait ( & m_oDoneAccepting, & m_oMutex ); MUTEX_UNLOCK ( m_oMutex ); } // if no one is accepting, we accept else { m_nAcceptedNewConnection = 0; //fprintf ( stderr, "Accepting\n" ); // we're now accepting m_nAccepting = 1; MUTEX_UNLOCK ( m_oMutex ); // create the FD set for poll CreateFdSet(); int nSocketCount = poll( m_oReadFds.GetFdArray(), m_oReadFds.GetFdCount(), inTimeoutMilliseconds ); // handle select error #ifdef _WIN32 if ( nSocketCount == SOCKET_ERROR ) #else // NOT _WIN32 if ( nSocketCount == -1 ) #endif // _WIN32 { EHS_TRACE ( "[%d] Critical Error: select() failed. Aborting\n", inThreadId ); // Idea! Remove stupid idea //exit ( 0 ); } // if no sockets have data to read, clear accepting flag and return if ( nSocketCount > 0 ) { // Check the accept socket for a new connection CheckAcceptSocket ( ); // check client sockets for data CheckClientSockets ( ); } MUTEX_LOCK ( m_oMutex ); ClearIdleConnections ( ); m_nAccepting = 0; MUTEX_UNLOCK ( m_oMutex ); // Occasional pulse for updating of things m_poTopLevelEHS->HttpPulse (); } // END ACCEPTING } // END NO REQUESTS PENDING MUTEX_LOCK ( m_oMutex ); // Delete unused connections after all threads have been synced EHSConnectionList :: iterator iter = m_oEHSConnectionUnusedList.begin (); while ( iter != m_oEHSConnectionUnusedList.end () ) { EHSConnection* pConnection = *iter; // Delete it after all threads are past syncing point if ( pConnection->m_UnusedSyncId < m_ThreadsSyncPoint.GetSyncId () - 1 ) { iter = m_oEHSConnectionUnusedList.erase ( iter ); delete pConnection; } else iter++; } MUTEX_UNLOCK ( m_oMutex ); }
HttpRequest * EHSServer::GetNextRequest ( ) { // don't lock because it's only called from within locked sections HttpRequest * poNextRequest = NULL; // pick a random connection if the list isn't empty if ( ! m_oEHSConnectionList.empty ( ) ) { // pick a random connection, so no one takes too much time int nWhich = (int) ( ( (double) m_oEHSConnectionList.size ( ) ) * rand ( ) / ( RAND_MAX + 1.0 ) ); // go to that element EHSConnectionList::iterator i = m_oEHSConnectionList.begin ( ); int nCounter = 0; for ( nCounter = 0; nCounter < nWhich; nCounter++ ) { i++; } // now get the next available request treating the list as circular EHSConnectionList::iterator iStartPoint = i; int nFirstTime = 1; while ( poNextRequest == NULL && !( iStartPoint == i && nFirstTime == 0 ) ) { // check this one to see if it has anything poNextRequest = (*i)->GetNextRequest ( ); i++; if ( i == m_oEHSConnectionList.end ( ) ) { i = m_oEHSConnectionList.begin ( ); } nFirstTime = 0; // decrement the number of pending requests if ( poNextRequest != NULL ) { m_nRequestsPending--; } } } if ( poNextRequest == NULL ) { // EHS_TRACE ( "No request found\n" ); } else { EHS_TRACE ( "Found request\n" ); } return poNextRequest; }
EHSServer::EHSServer ( EHS * ipoTopLevelEHS ///< pointer to top-level EHS for request routing ) : m_nServerRunningStatus ( SERVERRUNNING_NOTRUNNING ), m_poTopLevelEHS ( ipoTopLevelEHS ), m_nRequestsPending ( 0 ), m_nIdleTimeout ( 15 ) { // you HAVE to specify a top-level EHS object // compare against NULL for 64-bit systems assert ( m_poTopLevelEHS != NULL ); MUTEX_SETUP ( m_oMutex ); pthread_cond_init ( & m_oDoneAccepting, NULL ); m_nAccepting = 0; m_iActiveThreadCount = 0; // grab out the parameters for less typing later on EHSServerParameters & roEHSServerParameters = ipoTopLevelEHS->m_oEHSServerParameters; // whether to run with https support int nHttps = roEHSServerParameters [ "https" ]; #ifdef EHS_MEMORY fprintf ( stderr, "[EHS_MEMORY] Allocated: EHSServer\n" ); #endif if ( nHttps ) { EHS_TRACE ( "EHSServer running in HTTPS mode\n"); } else { EHS_TRACE ( "EHSServer running in plain-text mode (no HTTPS)\n"); } // are we using secure sockets? if ( !nHttps ) { m_poNetworkAbstraction = new Socket ( ); } else { #ifdef COMPILE_WITH_SSL EHS_TRACE ( "Trying to create secure socket with certificate='%s' and passphrase='%s'\n", (const char*)roEHSServerParameters [ "certificate" ], (const char*)roEHSServerParameters [ "passphrase" ] ); SecureSocket * poSecureSocket = new SecureSocket ( roEHSServerParameters [ "certificate" ], roEHSServerParameters [ "passphrase" ] ); // HIGHLY EXPERIMENTAL // scary shit EHS_TRACE ( "Thinking about loading callback - '%s'\n", roEHSServerParameters [ "passphrasecallback" ]. GetCharString ( ) ); if ( roEHSServerParameters [ "passphrasecallback" ] != "" && dlsym ( RTLD_DEFAULT, roEHSServerParameters [ "passphrasecallback" ] ) == NULL ) { EHS_TRACE ( "Couldn't load symbol for '%s' -- make sure you extern \"C\" the function\n", roEHSServerParameters [ "passphrasecallback" ]. GetCharString ( ) ); } EHS_TRACE ( "done thinking about loading callback\n" ); poSecureSocket->SetPassphraseCallback ( (int ( * ) ( char *, int, int, void * )) dlsym ( RTLD_DEFAULT, roEHSServerParameters [ "passphrasecallback" ] ) ); // END EXPERIMENTAL m_poNetworkAbstraction = poSecureSocket; #else // COMPILE_WITH_SSL fprintf ( stderr, "EHS not compiled with SSL support. Cannot create HTTPS server. Aborting\n" ); exit ( 1 ); #endif // COMPILE_WITH_SSL } // initialize the socket assert ( m_poNetworkAbstraction != NULL ); int nResult = m_poNetworkAbstraction->Init ( roEHSServerParameters [ "bindip" ], roEHSServerParameters [ "port" ] ); // initialize socket stuff if ( nResult != NetworkAbstraction::INITSOCKET_SUCCESS ) { EHS_TRACE ( "Error: Failed to initialize sockets\n" ); return; } if ( roEHSServerParameters [ "mode" ] == "threadpool" ) { // need to set this here because the thread will check this to make // sure it's supposed to keep running m_nServerRunningStatus = SERVERRUNNING_THREADPOOL; // create a pthread int nResult = -1; EHS_TRACE ( "Starting %d threads\n", roEHSServerParameters [ "threadcount" ].GetInt ( ) ); int nThreadsToStart = roEHSServerParameters [ "threadcount" ].GetInt ( ); if ( nThreadsToStart == 0 ) { nThreadsToStart = 1; } for ( int i = 0; i < nThreadsToStart; i++ ) { EHS_TRACE ( "creating thread with %x, NULL, %x, %x\n", &m_nAcceptThreadId, EHSServer::PthreadHandleData_ThreadedStub, (void *) this ); // create new thread and detach so we don't have to join on it nResult = pthread_create ( &m_nAcceptThreadId, NULL, EHSServer::PthreadHandleData_ThreadedStub, (void *) this ); pthread_detach ( m_nAcceptThreadId ); } if ( nResult != 0 ) { m_nServerRunningStatus = SERVERRUNNING_NOTRUNNING; } } else if ( roEHSServerParameters [ "mode" ] == "onethreadperrequest" ) { m_nServerRunningStatus = SERVERRUNNING_ONETHREADPERREQUEST; // spawn off one thread just to deal with basic stuff nResult = pthread_create ( &m_nAcceptThreadId, NULL, EHSServer::PthreadHandleData_ThreadedStub, (void *) this ); pthread_detach ( m_nAcceptThreadId ); // check to make sure the thread was created properly if ( nResult != 0 ) { m_nServerRunningStatus = SERVERRUNNING_NOTRUNNING; } } else if ( roEHSServerParameters [ "mode" ] == "singlethreaded" ) { // we're single threaded m_nServerRunningStatus = SERVERRUNNING_SINGLETHREADED; } else { EHS_TRACE ( "INVALID 'mode' SPECIFIED.\ntMust be 'singlethreaded', 'threadpool', or 'onethreadperrequest'\n" ); assert ( 0 ); } if ( m_nServerRunningStatus == SERVERRUNNING_THREADPOOL ) { EHS_TRACE ( "Info: EHS Server running in dedicated thread mode with '%s' threads\n", roEHSServerParameters [ "threadcount" ] == "" ? roEHSServerParameters [ "threadcount" ].GetCharString ( ) : "1" ); } else if ( m_nServerRunningStatus == SERVERRUNNING_ONETHREADPERREQUEST ) { EHS_TRACE ( "Info: EHS Server running with one thread per request\n" ); } else if ( m_nServerRunningStatus == SERVERRUNNING_SINGLETHREADED ) { EHS_TRACE ( "Info: EHS Server running in non-dedicated thread mode\n" ); } else { EHS_TRACE ( "Error: EHS Server not running. Server initialization failed\n" ); } return; }
void EHSServer::CheckClientSockets ( ) { MUTEX_LOCK ( m_oMutex ); // go through all the sockets from which we're still reading for ( EHSConnectionList::iterator i = m_oEHSConnectionList.begin ( ); i != m_oEHSConnectionList.end ( ); i++ ) { if ( m_oReadFds.IsSet( (*i)->GetNetworkAbstraction()->GetFd(), POLLIN ) ) { if ( MUTEX_TRY_LOCK ( (*i)->m_oConnectionMutex ) == false ) continue; EHS_TRACE ( "$$$$$ Got data on client connection\n" ); // prepare a buffer for the read static const int BYTES_TO_READ_AT_A_TIME = 10240; char psReadBuffer [ BYTES_TO_READ_AT_A_TIME + 1 ]; memset ( psReadBuffer, 0, BYTES_TO_READ_AT_A_TIME + 1 ); // do the actual read int nBytesReceived = (*i)->GetNetworkAbstraction ( )->Read ( psReadBuffer, BYTES_TO_READ_AT_A_TIME ); if ( nBytesReceived == SOCKET_ERROR ) { int err = GetLastSocketError (); if ( err == E_WOULDBLOCK ) { MUTEX_UNLOCK ( (*i)->m_oConnectionMutex ); continue; } } // if we received a disconnect if ( nBytesReceived <= 0 ) { // we're done reading and we received a disconnect (*i)->DoneReading ( true ); } // otherwise we got data else { // take the data we got and append to the connection's buffer EHSConnection::AddBufferResult nAddBufferResult = (*i)->AddBuffer ( psReadBuffer, nBytesReceived ); // if add buffer failed, don't read from this connection anymore if ( nAddBufferResult == EHSConnection::ADDBUFFER_INVALIDREQUEST || nAddBufferResult == EHSConnection::ADDBUFFER_TOOBIG ) { // done reading but did not receieve disconnect EHS_TRACE ( "Done reading because we got a bad request\n" ); (*i)->DoneReading ( false ); } // end error with AddBuffer } // end nBytesReceived MUTEX_UNLOCK ( (*i)->m_oConnectionMutex ); } // FD_ISSET } // for loop through connections MUTEX_UNLOCK ( m_oMutex ); }