/* -*-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 <stdio.h> #include <unistd.h> #include <netdb.h> /* gethostbyname */ #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <fcntl.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <iostream> #include <sstream> #include <pthread.h> #include <assert.h> #include <sys/select.h> #include <stdarg.h> #include <sys/time.h> #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, "<head>" ); if ( !!cfg ) { int refreshSecs; if ( cfg->GetValueFor( "WWW_REFRESH_SECS", &refreshSecs ) ) { fprintf( fil, "<meta http-equiv=\"refresh\" content=\"%d\" />", 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, "<title>relay: %d/%d</title>\n", info->m_nRoomsFilled, info->m_nCrefsCurrent ); fprintf( fil, "</head>" ); } static void printTail( FILE* fil ) { char buf[128]; /* print version and uptime */ fprintf( fil, "<div class=\"header\">Relay version</div>" ); format_rev( buf, sizeof(buf) ); fprintf( fil, "<p>%s</p>", buf ); } static void printCrefs( FILE* fil, const CrefMgrInfo* info, bool isLocal ) { fprintf( fil, "<div class=\"header\">Connections</div>" ); fprintf( fil, "<table><tr>" ); fprintf( fil, "<th>Room</th>" "<th>Lang</th>" "<th>ConnName</th>" "<th>ID</th>" "<th>For</th>" "<th>Bytes</th>" "<th>Expect</th>" "<th>Here</th>" "<th>State</th>" "<th>Host IDs</th>" "<th>Seeds</th>" ); if ( isLocal ) { fprintf( fil, "<th>Host IPs</th>" ); } fprintf( fil, "</tr>\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, "<tr>" "<td>%s</td>" /* name */ "<td>%d</td>" /* lang */ "<td>%s</td>" /* conn name */ "<td>%d</td>" /* cookie id */ "<td>%s</td>" /* conntime */ "<td>%d</td>" /* total sent */ "<td>%d</td>" /* players */ "<td>%d</td>" /* players here */ "<td>%s</td>" /* State */ "<td>%s</td>" /* Hosts */ "<td>%s</td>" /* Seeds */ , crefInfo->m_cookie.c_str(), crefInfo->m_langCode, crefInfo->m_connName.c_str(), crefInfo->m_cookieID, conntime, crefInfo->m_totalSent, crefInfo->m_nPlayersSought, crefInfo->m_nPlayersHere, stateString( crefInfo->m_curState ), crefInfo->m_hostsIds.c_str(), crefInfo->m_hostSeeds.c_str() ); if ( isLocal ) { fprintf( fil, "<td>%s</td>", /* Ip addrs */ crefInfo->m_hostIps.c_str() ); } fprintf( fil, "</tr>\n" ); } fprintf( fil, "</table>\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, "<div class=\"header\">Stats</div>" ); fprintf( fil, "<table>" ); fprintf( fil, "<tr>" "<th>Ports</th><th>Uptime</th><th>Spawns</th><th>Spawn Utime</th>" "<th>Rooms filled</th><th>Games in play</th></tr>" ); fprintf( fil, "<tr><td>%s</td><td>%s</td><td>%d</td>" "<td>%s</td><td>%d</td><td>%d</td></tr>\n", info->m_ports, uptime1, GetNSpawns(), uptime2, info->m_nRoomsFilled, info->m_nCrefsCurrent ); fprintf( fil, "</table>" ); } 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, "<html>" ); send_meta( fil, info ); fprintf( fil, "<body><div class=\"main\">" ); printStats( fil, info, isLocal ); printCrefs( fil, info, isLocal ); printTail( fil ); fprintf( fil, "</div></body></html>" ); 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 */