/* * Original work: Copyright (c) 2014, Oculus VR, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * RakNet License.txt file in the licenses directory of this source tree. An additional grant * of patent rights can be found in the RakNet Patents.txt file in the same directory. * * * Modified work: Copyright (c) 2016-2017, SLikeSoft UG (haftungsbeschränkt) * * This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style * license found in the license.txt file in the root directory of this source tree. */ /// \file /// \brief This file is a sample for using HTTPConnection and PHPDirectoryServer2 #include "slikenet/TCPInterface.h" #include "slikenet/HTTPConnection.h" #include "PHPDirectoryServer2.h" #include "slikenet/sleep.h" #include "slikenet/string.h" #include "slikenet/GetTime.h" #include "slikenet/DS_Table.h" #include #include #include #include "slikenet/Gets.h" #include "slikenet/Getche.h" #include "slikenet/linux_adapter.h" #include "slikenet/osx_adapter.h" using namespace SLNet; // Allocate rather than create on the stack or the RakString mutex crashes on shutdown TCPInterface *tcp; HTTPConnection *httpConnection; PHPDirectoryServer2 *phpDirectoryServer2; enum ReadResultEnum { RR_EMPTY_TABLE, RR_READ_TABLE, RR_TIMEOUT, }; ReadResultEnum ReadResult(SLNet::RakString &httpResult) { SLNet::TimeMS endTime= SLNet::GetTimeMS()+10000; httpResult.Clear(); while (SLNet::GetTimeMS()Receive(); if(packet) { httpConnection->ProcessTCPPacket(packet); tcp->DeallocatePacket(packet); } if (httpConnection->HasRead()) { httpResult = httpConnection->Read(); // Good response, let the PHPDirectoryServer2 class handle the data // If resultCode is not an empty string, then we got something other than a table // (such as delete row success notification, or the message is for HTTP only and not for this class). HTTPReadResult readResult = phpDirectoryServer2->ProcessHTTPRead(httpResult); if (readResult==HTTP_RESULT_GOT_TABLE) { //printf("RR_READ_TABLE\n"); return RR_READ_TABLE; } else if (readResult==HTTP_RESULT_EMPTY) { //printf("HTTP_RESULT_EMPTY\n"); return RR_EMPTY_TABLE; } } // Update our two classes so they can do time-based updates httpConnection->Update(); phpDirectoryServer2->Update(); // Prevent 100% cpu usage RakSleep(30); } return RR_TIMEOUT; } bool HaltOnUnexpectedResult(ReadResultEnum result, ReadResultEnum expected) { if (result!=expected) { printf("TEST FAILED. Expected "); switch (expected) { case RR_EMPTY_TABLE: printf("no results"); break; case RR_TIMEOUT: printf("timeout"); break; case RR_READ_TABLE: printf("to download result"); break; } switch (result) { case RR_EMPTY_TABLE: printf(". No results were downloaded"); break; case RR_READ_TABLE: printf(". Got a result"); break; case RR_TIMEOUT: printf(". Timeout"); break; } printf("\n"); return true; } return false; } void DownloadTable() { phpDirectoryServer2->DownloadTable("a"); } void UploadTable(SLNet::RakString gameName, unsigned short gamePort) { phpDirectoryServer2->UploadTable("a", gameName, gamePort, false); } void UploadAndDownloadTable(SLNet::RakString gameName, unsigned short gamePort) { phpDirectoryServer2->UploadAndDownloadTable("a", "a", gameName, gamePort, false); } bool PassTestOnEmptyDownloadedTable() { const DataStructures::Table *games = phpDirectoryServer2->GetLastDownloadedTable(); if (games->GetRowCount()==0) { printf("Test passed.\n"); return true; } printf("TEST FAILED. Empty table should have been downloaded.\n"); return false; } bool VerifyDownloadMatchesUpload(int requiredRowCount, int testRowIndex) { const DataStructures::Table *games = phpDirectoryServer2->GetLastDownloadedTable(); if (games->GetRowCount()!=(unsigned int)requiredRowCount) { printf("TEST FAILED. Expected %i result rows, got %i\n", requiredRowCount, games->GetRowCount()); return false; } SLNet::RakString columnName; SLNet::RakString value; unsigned int i; DataStructures::Table::Row *row = games->GetRowByIndex(testRowIndex,NULL); const DataStructures::List& columns = games->GetColumns(); unsigned int colIndex; // +4 comes from automatic fields // _GAME_PORT // _GAME_NAME // _SYSTEM_ADDRESS // __SEC_AFTER_EPOCH_SINCE_LAST_UPDATE if (phpDirectoryServer2->GetFieldCount()+4!=games->GetColumnCount()) { printf("TEST FAILED. Expected %i columns, got %i\n", phpDirectoryServer2->GetFieldCount()+4, games->GetColumnCount()); printf("Expected columns:\n"); for (colIndex=0; colIndex < phpDirectoryServer2->GetFieldCount(); colIndex++) { phpDirectoryServer2->GetField(colIndex, columnName, value); printf("%i. %s\n", colIndex+1, columnName.C_String()); } printf("%i. _GAME_PORT\n", colIndex++); printf("%i. _GAME_NAME\n", colIndex++); printf("%i. _System_Address\n", colIndex++); printf("%i. __SEC_AFTER_EPOCH_SINCE_LAST_UPDATE\n", colIndex++); printf("Got columns:\n"); for (colIndex=0; colIndex < columns.Size(); colIndex++) { printf("%i. %s\n", colIndex+1, columns[colIndex].columnName); } return false; } for (i=0; i < phpDirectoryServer2->GetFieldCount(); i++) { phpDirectoryServer2->GetField(i, columnName, value); for (colIndex=0; colIndex < columns.Size(); colIndex++) { if (strcmp(columnName.C_String(), columns[colIndex].columnName)==0) break; } if (colIndex==columns.Size()) { printf("TEST FAILED. Expected column with name %s\n", columnName.C_String()); return false; } if (strcmp(value.C_String(), row->cells[colIndex]->c)!=0) { printf("TEST FAILED. Expected row with value '%s' at index %i for column %s. Got '%s'.\n", value.C_String(), i, columnName.C_String(), row->cells[colIndex]->c); return false; } } printf("Test passed.\n"); return true; } void PrintHttpResult(SLNet::RakString httpResult) { printf("--- Last result read ---\n"); printf("%s", httpResult.C_String()); } void PrintFieldColumns(void) { unsigned int colIndex; SLNet::RakString columnName; SLNet::RakString value; for (colIndex=0; colIndex < phpDirectoryServer2->GetFieldCount(); colIndex++) { phpDirectoryServer2->GetField(colIndex, columnName, value); printf("%i. %s\n", colIndex+1, columnName.C_String()); } } bool RunTest() { SLNet::RakString httpResult; ReadResultEnum rr; char ch[32]; printf("Warning, table must be clear before starting the test.\n"); printf("Press enter to start\n"); Gets(ch,sizeof(ch)); printf("*** Testing initial table is empty.\n"); // Table should start emptyF DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE)) {PrintHttpResult(httpResult); return false;} if (PassTestOnEmptyDownloadedTable()==false) {PrintHttpResult(httpResult); return false;} printf("*** Downloading again, to ensure download does not modify the table.\n"); // Downloading should not modify the table DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE)) {PrintHttpResult(httpResult); return false;} if (PassTestOnEmptyDownloadedTable()==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing upload.\n"); // Upload values likely to mess up PHP phpDirectoryServer2->SetField("TestField1","0"); phpDirectoryServer2->SetField("TestField2",""); phpDirectoryServer2->SetField("TestField3"," "); phpDirectoryServer2->SetField("TestField4","!@#$%^&*("); phpDirectoryServer2->SetField("TestField5","A somewhat big long string as these things typically go.\nIt even has a linebreak!"); phpDirectoryServer2->SetField("TestField6","="); phpDirectoryServer2->UploadTable("a", "FirstGameUpload", 80, false); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE)) {PrintHttpResult(httpResult); return false;} if (PassTestOnEmptyDownloadedTable()==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing download, should match upload exactly.\n"); // Download what we just uploaded DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE)) {PrintHttpResult(httpResult); return false;} // Check results if (VerifyDownloadMatchesUpload(1,0)==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing that download works twice in a row.\n"); // Make sure download works twice DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE)) {PrintHttpResult(httpResult); return false;} // Check results if (VerifyDownloadMatchesUpload(1,0)==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing reuploading a game to modify fields.\n"); // Modify fields phpDirectoryServer2->SetField("TestField1","zero"); phpDirectoryServer2->SetField("TestField2","empty"); phpDirectoryServer2->SetField("TestField3","space"); phpDirectoryServer2->SetField("TestField4","characters"); phpDirectoryServer2->SetField("TestField5","A shorter string"); phpDirectoryServer2->SetField("TestField6","Test field 6"); phpDirectoryServer2->UploadTable("a", "FirstGameUpload", 80, false); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE)) {PrintHttpResult(httpResult); return false;} if (PassTestOnEmptyDownloadedTable()==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing that downloading returns modified fields.\n"); // Download what we just uploaded DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE)) {PrintHttpResult(httpResult); return false;} // Check results if (VerifyDownloadMatchesUpload(1,0)==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing that downloading works twice.\n"); // Make sure download works twice DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE)) {PrintHttpResult(httpResult); return false;} // Check results if (VerifyDownloadMatchesUpload(1,0)==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing upload of a second game.\n"); // Upload another game phpDirectoryServer2->SetField("TestField1","0"); phpDirectoryServer2->SetField("TestField2",""); phpDirectoryServer2->SetField("TestField3"," "); phpDirectoryServer2->SetField("TestField4","Game two characters !@#$%^&*("); phpDirectoryServer2->UploadTable("a", "SecondGameUpload", 80, false); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE)) {PrintHttpResult(httpResult); return false;} if (PassTestOnEmptyDownloadedTable()==false) {PrintHttpResult(httpResult); return false;} SLNet::TimeMS startTime = SLNet::GetTimeMS(); printf("*** Testing 20 repeated downloads.\n"); //printf("Field columns\n"); //PrintFieldColumns(); // Download repeatedly unsigned int downloadCount=0; while (downloadCount < 20) { printf("*** (%i) Downloading 'FirstGameUpload'\n", downloadCount+1); // Download again (First game) DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE)) {PrintHttpResult(httpResult); return false;} // Check results // DOn't have this stored anymore // if (VerifyDownloadMatchesUpload(2,0)==false) // {PrintHttpResult(httpResult); return false;} printf("*** (%i) Downloading 'SecondGameUpload'\n", downloadCount+1); // Download again (second game) DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE)) {PrintHttpResult(httpResult); return false;} // Check results if (VerifyDownloadMatchesUpload(2,1)==false) {PrintHttpResult(httpResult); return false;} downloadCount++; RakSleep(1000); } printf("*** Waiting for 70 seconds to have elapsed...\n"); RakSleep(70000 - (SLNet::GetTimeMS()-startTime)); printf("*** Testing that table is now clear.\n"); // Table should be cleared DownloadTable(); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE)) {PrintHttpResult(httpResult); return false;} if (PassTestOnEmptyDownloadedTable()==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing upload and download. No games should be downloaded.\n"); phpDirectoryServer2->ClearFields(); phpDirectoryServer2->SetField("TestField1","NULL"); UploadAndDownloadTable("FirstGameUpload", 80); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_EMPTY_TABLE)) {PrintHttpResult(httpResult); return false;} if (PassTestOnEmptyDownloadedTable()==false) {PrintHttpResult(httpResult); return false;} printf("*** Testing upload and download. One game should be downloaded.\n"); UploadAndDownloadTable("ThirdGameUpload", 80); if (HaltOnUnexpectedResult(rr=ReadResult(httpResult), RR_READ_TABLE)) {PrintHttpResult(httpResult); return false;} if (VerifyDownloadMatchesUpload(1,0)==false) {PrintHttpResult(httpResult); return false;} return true; } void OutputBody(HTTPConnection& http, const char *path, const char *data, TCPInterface& tcp); void TestPHPDirectoryServer(int argc, char **argv) { printf("PHP Directory server 2.\n"); printf("Similar to lightweight database, but uses common shared webservers.\n"); printf("Set columns and one row for your game, and upload it to a\nviewable and downloadable webpage.\n"); printf("Difficulty: Intermediate\n\n"); // tcp = SLNet::OP_NEW(_FILE_AND_LINE_); // httpConnection = SLNet::OP_NEW(_FILE_AND_LINE_); // phpDirectoryServer2 = SLNet::OP_NEW(_FILE_AND_LINE_); // SLNet::TimeMS lastTouched = 0; char website[256]; char pathToPHP[256]; if (argc==3) { strcpy_s(website, argv[1]); strcpy_s(pathToPHP, argv[2]); } else { printf("Enter website, e.g. jenkinssoftware.com:\n"); Gets(website,sizeof(website)); if (website[0]==0) strcpy_s(website, "jenkinssoftware.com"); printf("Enter path to DirectoryServer.php, e.g. raknet/DirectoryServer.php:\n"); Gets(pathToPHP,sizeof(pathToPHP)); if (pathToPHP[0]==0) strcpy_s(pathToPHP, "/raknet/DirectoryServer.php"); } if (website[strlen(website)-1]!='/' && pathToPHP[0]!='/') { memmove(pathToPHP+1, pathToPHP, strlen(pathToPHP)+1); pathToPHP[0]='/'; } // This creates an HTTP connection using TCPInterface. It allows you to Post messages to and parse messages from webservers. // The connection attempt is asynchronous, and is handled automatically as HTTPConnection::Update() is called httpConnection->Init(tcp, website); // This adds specific parsing functionality to HTTPConnection, in order to communicate with DirectoryServer.php phpDirectoryServer2->Init(httpConnection, pathToPHP); if (RunTest()) { printf("All tests passed.\n"); } char str[256]; do { printf("\nPress q to quit.\n"); Gets(str, sizeof(str)); } while (str[0]!='q'); // The destructor of each of these references the other, so delete in this order SLNet::OP_DELETE(phpDirectoryServer2,_FILE_AND_LINE_); SLNet::OP_DELETE(httpConnection,_FILE_AND_LINE_); SLNet::OP_DELETE(tcp,_FILE_AND_LINE_); } void TestGet(void) { printf("This is NOT a reliable way to download from a website. Use libcurl instead.\n"); httpConnection->Init(tcp, "jenkinssoftware.com"); httpConnection->Get("/trivia/ranking.php?t=single&places=6&top"); for(;;) { Packet *packet = tcp->Receive(); if(packet) { //printf((char*) packet->data); httpConnection->ProcessTCPPacket(packet); tcp->DeallocatePacket(packet); } httpConnection->Update(); if (httpConnection->IsBusy()==false) { RakString fileContents = httpConnection->Read(); printf(fileContents.C_String()); _getche(); return; } // Prevent 100% cpu usage RakSleep(30); } } int main(int argc, char **argv) { printf("PHP Directory server 2.\n"); printf("Similar to lightweight database, but uses common shared webservers.\n"); printf("Set columns and one row for your game, and upload it to a\nviewable and downloadable webpage.\n"); printf("Difficulty: Intermediate\n\n"); tcp = SLNet::OP_NEW(__FILE__,__LINE__); httpConnection = SLNet::OP_NEW(__FILE__,__LINE__); phpDirectoryServer2 = SLNet::OP_NEW(__FILE__,__LINE__); // RakNetTime lastTouched = 0; // Start the TCP thread. This is used for general TCP communication, whether it is for webpages, sending emails, or telnet tcp->Start(0, 64); TestPHPDirectoryServer(argc,argv); //TestGet(); return 0; }