/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ /* * Copyright 2009 by Eric House (xwords@eehouse.org). All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef DO_HTTP #include #include #include /* gethostbyname */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ctrl.h" #include "cref.h" #include "crefmgr.h" #include "mlock.h" #include "xwrelay_priv.h" #include "configs.h" #include "lstnrmgr.h" #include "http.h" /* * http://www.jbox.dk/sanos/webserver.htm has code for a trivial web server. Good example. */ static void send_header( FILE* fil, const char* title ) { fprintf( fil, "HTTP/1.0 %d %s\r\n", 200, title ); fprintf( fil, "Server: xwrelay\r\n" ); fprintf( fil, "Content-Type: text/html\r\n" ); fprintf( fil, "Connection: close\r\n"); fprintf( fil, "\r\n"); } static void send_meta( FILE* fil, const CrefMgrInfo* info ) { FILE* css; RelayConfigs* cfg = RelayConfigs::GetConfigs(); char pathbuf[256] = { '\0' }; fprintf( fil, "" ); if ( !!cfg ) { int refreshSecs; if ( cfg->GetValueFor( "WWW_REFRESH_SECS", &refreshSecs ) ) { fprintf( fil, "", refreshSecs ); } (void)cfg->GetValueFor( "WWW_CSS_PATH", pathbuf, sizeof(pathbuf) ); } if ( pathbuf[0] ) { css = fopen( pathbuf, "r" ); if ( NULL != css ) { for ( ; ; ) { char buf[256]; size_t nread = fread( buf, 1, sizeof(buf), css ); if ( nread <= 0 ) { break; } (void) fwrite( buf, 1, nread, fil ); } fclose( css ); } } fprintf( fil, "relay: %d/%d\n", info->m_nRoomsFilled, info->m_nCrefsCurrent ); fprintf( fil, "" ); } static void printTail( FILE* fil ) { char buf[128]; /* print version and uptime */ fprintf( fil, "
Relay version
" ); format_rev( buf, sizeof(buf) ); fprintf( fil, "

%s

", buf ); } static void printCrefs( FILE* fil, const CrefMgrInfo* info, bool isLocal ) { fprintf( fil, "
Connections
" ); fprintf( fil, "" ); fprintf( fil, "" "" "" "" "" "" "" "" "" "" "" ); if ( isLocal ) { fprintf( fil, "" ); } fprintf( fil, "\n" ); time_t curTime = uptime(); int ii; for ( ii = info->m_crefInfo.size() - 1; ii >= 0; --ii ) { const CrefInfo* crefInfo = &info->m_crefInfo[ii]; char conntime[32]; format_uptime( curTime - crefInfo->m_startTime, conntime, sizeof(conntime) ); fprintf( fil, "" "" /* name */ "" /* lang */ "" /* conn name */ "" /* cookie id */ "" /* conntime */ "" /* players */ "" /* players here */ "" /* State */ "" /* Hosts */ "" /* Seeds */ , crefInfo->m_cookie.c_str(), crefInfo->m_langCode, crefInfo->m_connName.c_str(), crefInfo->m_cookieID, conntime, crefInfo->m_nPlayersSought, crefInfo->m_nPlayersHere, stateString( crefInfo->m_curState ), crefInfo->m_hostsIds.c_str(), crefInfo->m_hostSeeds.c_str() ); if ( isLocal ) { fprintf( fil, "", /* Ip addrs */ crefInfo->m_hostIps.c_str() ); } fprintf( fil, "\n" ); } fprintf( fil, "
RoomLangConnNameIDForBytesExpectHereStateHost IDsSeedsHost IPs
%s%d%s%d%s%d%d%s%s%s%s
\n" ); } /* printCrefs */ static void printStats( FILE* fil, const CrefMgrInfo* info, bool isLocal ) { char uptime1[64]; char uptime2[64]; format_uptime( uptime(), uptime1, sizeof(uptime1) ); format_uptime( time(NULL) - info->m_startTimeSpawn, uptime2, sizeof(uptime2) ); fprintf( fil, "
Stats
" ); fprintf( fil, "" ); fprintf( fil, "" "" "" ); fprintf( fil, "" "\n", info->m_ports, uptime1, GetNSpawns(), uptime2, info->m_nRoomsFilled, info->m_nCrefsCurrent ); fprintf( fil, "
PortsUptimeSpawnsSpawn UtimeRooms filledGames in play
%s%s%d%s%d%d
" ); } class HttpInstance { public: HttpInstance( int sock, HttpState* state ) { m_sock = sock; m_state = state; } int m_sock; HttpState* m_state; }; static void* http_thread_main( void* arg ) { blockSignals(); HttpInstance* inst = (HttpInstance*)arg; HttpState* state = inst->m_state; int sock = inst->m_sock; char buf[512]; ssize_t totalRead = 0; while ( totalRead <= 4 ) { /* have we read enough for GET? */ ssize_t nread = read( sock, buf+totalRead, sizeof(buf)-1-totalRead ); if ( nread == 0 ) { // EOF break; } else if ( nread > 0 ) { buf[nread] = '\0'; } else { logf( XW_LOGERROR, "%s: read() got error: %s", __func__, strerror(errno) ); break; } totalRead += nread; } if ( 0 == strncasecmp( "GET ", buf, 3 ) ) { struct sockaddr_in name; socklen_t namelen = sizeof(name); bool isLocal = 0 == getpeername( sock, (struct sockaddr*)&name, &namelen ); if ( isLocal ) { in_addr_t s_addr = name.sin_addr.s_addr; isLocal = 0x7f000001 == htonl(s_addr); } MutexLock ml(&state->m_dataMutex); /* We'll handle as many http connections as folks want to throw at us, but will only fetch from the crefmgr infrequently, caching the data for next time. Only one thread at a time gets to read from it, ensuring we don't nuke it from under somebody. */ time_t curTime = time( NULL ); if ( state->m_nextFetch < curTime ) { delete state->m_crefInfo; state->m_crefInfo = NULL; } if ( state->m_crefInfo == NULL ) { state->m_crefInfo = new CrefMgrInfo(); CRefMgr::Get()->GetStats( *state->m_crefInfo ); state->m_nextFetch = curTime + state->m_sampleInterval; } const CrefMgrInfo* info = state->m_crefInfo; FILE* fil = fdopen( sock, "r+" ); fseek( fil, 0, SEEK_CUR ); // reverse stream send_header( fil, "status page" ); fprintf( fil, "" ); send_meta( fil, info ); fprintf( fil, "
" ); printStats( fil, info, isLocal ); printCrefs( fil, info, isLocal ); printTail( fil ); fprintf( fil, "
" ); fclose( fil ); } else { logf( XW_LOGINFO, "NOT a GET" ); } close( sock ); delete inst; return NULL; } /* http_thread_main */ void run_http_thread( HttpState* state ) { pthread_t thread; sockaddr newaddr; socklen_t siz = sizeof(newaddr); int sock = accept( state->ctrl_sock, &newaddr, &siz ); HttpInstance* inst = new HttpInstance( sock, state ); int result = pthread_create( &thread, NULL, http_thread_main, (void*)inst ); if ( 0 == result ) { pthread_detach( thread ); } else { logf( XW_LOGERROR, "%s: pthread_create failed: %s", __func__, strerror(errno) ); } } /* run_http_thread */ #endif /* DO_HTTP */