#include <common.h> #include <CGI/ServiceFCGI.h> #include <FCGI/RecordReader.h> #include <FCGI/MapOfSocketHandler.h> #include <FCGI/limits.h> #include <sys/epoll.h> #define ON_ERRNO() recordError(__LINE__,errno) #define ON_ERROR(S) recordError(__LINE__,(S)) static void recordError(int iLine,int iError) { TRACE_PRINT(("*" __FILE__ "(%d) - ERROR(%d) %s",iLine,iError,::strerror(iError))); } static void recordError(int iLine,const char* sError) { TRACE_PRINT(("*" __FILE__ "(%d) - ERROR %s",iLine,sError)); } enum { MAX_EVENTS = 20, MAX_LISTEN_BACKLOG = 100 }; SOCKET CGI::ServiceFCGI::attachListener(char* sSpec) { if (!sSpec) { // A usual case when started by the web server as a FastCGI backend. SOCKET socket = STDIN_FILENO; if (!makeNonBlocking(socket)) { ON_ERRNO(); return false; } if (0 != ::listen(socket,MAX_LISTEN_BACKLOG)) { ON_ERRNO(); return false; } return true; } char* p = ::strchr(sSpec,':'); const char* sAddress = "127.0.0.1"; const char* sPort = "4012"; // 0x0FAC ~ FAstCgi if (!p) { sAddress = sSpec; sPort = ""; } else if (sSpec < p) { sAddress = sSpec; *p++ = 0; sPort = p; } else { sPort = sSpec + 1; } // Address might be an INET address. struct addrinfo hints; memset(&hints,0,sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; struct addrinfo* pList = 0; int v = getaddrinfo(sAddress,sPort,&hints,&pList); if (0 != v) { ON_ERROR(gai_strerror(v)); return false; } ON_ERROR("Cannot connect listener!"); struct addrinfo* pInfo = pList; for (; pInfo; pInfo = pInfo->ai_next) { SOCKET socket = ::socket(pInfo->ai_family,pInfo->ai_socktype,pInfo->ai_protocol); if (socket < 0) { ON_ERRNO(); continue; } if (!makeNonBlocking(socket)) { ON_ERRNO(); continue; } // Set socket option so we can reuse the port on server crash. // This may fail for non-TCP/IP sockets, so ignore errors. int v = 1; setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&v,sizeof(v)); if (0 != ::bind(socket,pInfo->ai_addr,pInfo->ai_addrlen)) { ON_ERRNO(); close(socket); continue; } if (0 != ::listen(socket,MAX_LISTEN_BACKLOG)) { ON_ERRNO(); close(socket); continue; } break; } freeaddrinfo(pList); return !!pInfo; } bool CGI::ServiceFCGI::initialize() { bPollContinue = true; dtPollTimeout = 3000; // Verify the wanted FastCGI role is something we can handle. const char* sRole = ::getenv("FCGI_ROLE"); if (!sRole) { ON_ERROR("FCGI_ROLE value not specified."); return false; } if (0 != ::strcasecmp(sRole,"RESPONDER")) { // We do not handle the other roles as yet. ON_ERROR("FCGI_ROLE value not supported."); return false; } // Should do something with this. // const char* sWebServerAddresses = ::getenv("FCGI_WEB_SERVER_ADDRS"); char* sListener = ::getenv("FCGI_LISTENER"); SOCKET socket = attachListener(sListener); if (socket < 0) { return false; } // Allocate I/O event notification handle. hPoll = epoll_create1(EPOLL_CLOEXEC); if (hPoll < 0) { ON_ERRNO(); return false; } // Watch for incoming connections on socketListener. { struct epoll_event ev; ev.events = EPOLLIN; AcceptHandler* handler = mapOfHandlers->makeAcceptHandler(socket,hPoll); ev.data.u64 = handler->iToken; // Direct mapping would be more efficient, but not safe until throughly debugged. // ev.data.ptr = mapOfHandlers[0] = new AcceptHandler(hPoll,socket); if (epoll_ctl(hPoll,EPOLL_CTL_ADD,socket,&ev)) { ON_ERRNO(); return false; } } return true; } static void onTimeout() { TRACE_FN(CGI::ServiceFCGI::onTimeout); Trace::rolloverLog(); // TODO } int CGI::ServiceFCGI::dispatch() { TRACE_FN(CGI::ServiceFCGI::dispatch); struct epoll_event arrayEvents[MAX_EVENTS]; while (bPollContinue) { int nEvents = epoll_wait(hPoll,arrayEvents,MAX_EVENTS,dtPollTimeout); if (nEvents < 0) { ON_ERRNO(); return 1; } if (0 == nEvents) { onTimeout(); continue; } for (int i = 0; i < nEvents; ++i) { struct epoll_event* event = arrayEvents + i; int iToken = event->data.u32; SocketHandler* handler = mapOfHandlers[(FCGI::MAX_CONNECTIONS_DOMAIN - 1) & iToken]; if (iToken == handler->iToken) { if (EPOLLIN & event->events) { handler->onRead(); } if (EPOLLOUT & event->events) { handler->onWrite(); } } } } return 0; } void CGI::ServiceFCGI::cleanup() { close(hPoll); // TODO }