///////////////////////////////////////////////////////////////////////////// // Name: BaseServ.cpp // Purpose: Simple base Server // Author: Guy // Modified by: // Created: 21/08/2002 // Copyright: (c) 2002 Guy // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ========================================================================== // declarations // ========================================================================== // -------------------------------------------------------------------------- // headers // -------------------------------------------------------------------------- #include "baseserv.h" #include #include #include #include #include #include #ifdef WIN32 #include #endif #include /////////////////////////////////////////////////////////// // Utilities /////////////////////////////////////////////////////////// TBaseServerApp& GetServerApp() { return (TBaseServerApp&)*wxTheApp; } wxString Date2String(const wxDateTime& date) { return date.Format("%d-%m-%Y"); } wxDateTime String2Date(const wxString& str) { int d, m, y; sscanf(str, "%d-%d-%d", &d, &m, &y); wxDateTime date(d, wxDateTime::Month(m-1), y); return date; } wxSocketBase& operator<<(wxSocketBase& outf, const wxChar* str) { if (str && *str) outf.Write(str, (wxUint32)wxStrlen(str)); return outf; } wxSocketBase& operator<<(wxSocketBase& outf, wxString str) { if (!str.IsEmpty()) outf.Write(str, (wxUint32)str.Length()); return outf; } wxSocketBase& operator<<(wxSocketBase& outf, size_t num) { return outf << wxString::Format("%u", num); } // -------------------------------------------------------------------------- // classes // -------------------------------------------------------------------------- /////////////////////////////////////////////////////////// // THashTable /////////////////////////////////////////////////////////// void THashTable::Put(const wxString& key, const wxString& value) { THashString* entry = new THashString(value); m_Hash.Put(key, entry); } wxString THashTable::Get(const wxChar* key) const { wxString strValue; THashString* str = (THashString*)m_Hash.Get(key); if (str != NULL) strValue = str->m_str; return strValue; } THashTable::THashTable(size_t size) : m_Hash(wxKEY_STRING, size) { m_Hash.DeleteContents(true); } #ifdef WIN32 /////////////////////////////////////////////////////////// // TTaskbarIcon /////////////////////////////////////////////////////////// BEGIN_EVENT_TABLE(TTaskBarIcon, wxTaskBarIcon) EVT_TASKBAR_LEFT_DOWN(TTaskBarIcon::OnTaskBarClick) END_EVENT_TABLE() void TTaskBarIcon::OnTaskBarClick(wxTaskBarIconEvent& WXUNUSED(e)) { wxString url; // wxIPV4address addr; addr.AnyAddress(); // addr.Service(GetServerApp().GetDefaultPort()); // url << addr.Hostname() << ":" << addr.Service(); url << "http://127.0.0.1:" << GetServerApp().GetDefaultPort(); ::ShellExecute(0, "open", url, NULL, NULL, SW_SHOWNORMAL); } void TTaskBarIcon::Init() { const TBaseServerApp& a = GetServerApp(); wxIcon icon; const wxString strIcon = a.GetConfigString("Icon"); if (wxFileExists(strIcon)) { wxInitAllImageHandlers(); wxImage img(strIcon); img.Rescale(32,32); const wxBitmap bmp(img); icon.CopyFromBitmap(bmp); } else icon.LoadFile("soap", wxBITMAP_TYPE_ICO_RESOURCE); SetIcon(icon, a.GetAppName()); } #endif /////////////////////////////////////////////////////////// // TBaseServerApp /////////////////////////////////////////////////////////// enum { SERVER_ID = 1001, SOCKET_ID = 1002 }; BEGIN_EVENT_TABLE(TBaseServerApp, wxApp) EVT_SOCKET(SERVER_ID, TBaseServerApp::OnServerEvent) EVT_SOCKET(SOCKET_ID, TBaseServerApp::OnSocketEvent) EVT_IDLE(TBaseServerApp::OnIdle) END_EVENT_TABLE() void TBaseServerApp::WriteLog(const wxChar* str) const { if (m_log != NULL) *m_log << str << endl; } wxString TBaseServerApp::GetTempFilename(const wxChar * ext) { wxString strTmp; if (m_strTempDir.IsEmpty()) { const wxString strPrefix = "Srv"; ::wxGetTempFileName(strPrefix, strTmp); ::wxRemoveFile(strTmp); (wxString&)m_strTempDir << ::wxPathOnly(strTmp) << "/"; } strTmp = m_strTempDir; strTmp += wxString::Format("%s_%d.%s", GetAppName(), m_nTmpCounter, ext); m_nTmpCounter++; if (m_nTmpCounter >= 32) m_nTmpCounter = 0; return strTmp; } TXmlItem& TBaseServerApp::AddLogo(TXmlItem& tr) const { TXmlItem& td = tr.AddChild("td"); td.SetAttr("width", "15%"); td.SetAttr("align", "center"); TXmlItem& a = td.AddChild("a"); a.SetAttr("href", "/index.htm"); const wxString gif = GetConfigString("Icon", "euro.gif"); TXmlItem& img = a.AddChild("img"); img.SetAttr("src", gif); img.SetAttr("border", "0"); img.SetAttr("alt", "Return to Home Page"); return td; } TXmlItem& TBaseServerApp::CreatePageBody(TXmlItem& html, wxString header) const { if (header.IsEmpty()) header << GetAppName() << " Server"; html.SetTag("html"); TXmlItem& head = html.AddChild("head"); TXmlItem& meta1 = head.AddChild("meta"); meta1.SetAttr("name", "GENERATOR"); meta1.SetAttr("content", GetAppName()); TXmlItem& meta2 = head.AddChild("meta"); meta2.SetAttr("HTTP-EQUIV", "Content-Type"); meta2.SetAttr("content", "text/html; charset=iso-8859-1"); head.AddChild("title") << header; TXmlItem& body = html.AddChild("body"); body.SetAttr("bgcolor", "#EFCEAD"); if (m_bRunning) body.SetAttr("background", "back.gif"); TXmlItem& title = body.AddChild("table"); title.SetAttr("border", "5"); title.SetAttr("width", "100%"); TXmlItem& title_tr = title.AddChild("tr"); if (m_bRunning) AddLogo(title_tr); TXmlItem& td1 = title_tr.AddChild("td"); td1.AddChild("h1").AddChild("center") << header; if (m_bRunning) AddLogo(title_tr); body.AddChild("br"); return body; } TXmlItem& TBaseServerApp::AddLinkButton(TXmlItem& body, const wxChar* strCaption, const wxChar* strHref) const { TXmlItem& table = body.AddChild("table"); table.SetAttr("border", "2"); table.SetAttr("bgcolor", "#C0C0C0"); TXmlItem& td = table.AddChild("tr").AddChild("td"); td.SetAttr("align", "center"); td.AddChild("a").SetAttr("href", strHref) << strCaption; return table; } void TBaseServerApp::MessageBox(const wxChar* caption, const wxChar* msg, wxSocketBase& outs) { TXmlItem html; TXmlItem& body = CreatePageBody(html); TXmlItem& table = body.AddChild("center").AddChild("table"); table.SetAttr("width", "70%"); table.SetAttr("border", "2"); table.AddChild("caption").AddChild("h2") << caption; TXmlItem& td0 = table.AddChild("tr").AddChild("td"); td0.SetAttr("align", "justify"); td0.AddChild("h3") << msg; TXmlItem& td1 = table.AddChild("tr").AddChild("td"); td1.SetAttr("align", "center"); AddLinkButton(td1, "Return to main page", "index.htm"); const wxString strTmp = GetTempFilename(); html.Save(strTmp); SendFile(strTmp, outs); } bool TBaseServerApp::CanProcessCommand(wxString& cmd, wxSocketBase& outs) { if (cmd.Find("stop.cgi") > 0) { m_bRunning = false; // Disable gif on title if (outs.IsOk()) MessageBox("Warning!", "Server Stopped", outs); ExitMainLoop(); return false; } return true; } void TBaseServerApp::SendContent(wxFileInputStream& inf, wxSocketBase& sock) { const size_t nSize = inf.GetSize(); WriteLog(wxString::Format("Sending %lu bytes", nSize)); const size_t BUF_TEMP_SIZE = 1024*1024; // was 1024*16 char* buf = new char[BUF_TEMP_SIZE]; size_t bytes = BUF_TEMP_SIZE; size_t nTotalWritten = 0; while (bytes == BUF_TEMP_SIZE) { bytes = inf.Read(buf, bytes).LastRead(); size_t nWritten = sock.Write(buf, wxUint32(bytes)).LastCount(); nTotalWritten += nWritten; } delete buf; if (nTotalWritten < nSize) WriteLog(wxString::Format("I sent %lu of %lu bytes only.", nTotalWritten, nSize)); } void TBaseServerApp::SendFile(wxString strFilename, wxSocketBase& sock) { const bool bFound = wxFileExists(strFilename); if (!bFound) { const wxString strTmp = GetTempFilename(); if (!strTmp.IsEmpty()) { TXmlItem html; TXmlItem& body = CreatePageBody(html); body.AddChild("h1") << "Sorry: File not found!"; body.AddChild("br"); body.AddChild("h2") << strFilename; html.Save(strTmp); } strFilename = strTmp; } wxString strType; wxSplitPath(strFilename, NULL, NULL, &strType); wxFileType* type = wxTheMimeTypesManager->GetFileTypeFromExtension(strType); if (type != NULL) type->GetMimeType(&strType); else strType = "text/plain"; const wxDateTime date = ::wxFileModificationTime(strFilename); wxFileInputStream inf(strFilename); const size_t nSize = inf.GetSize(); if (bFound) sock << "HTTP/1.1 200 OK\n"; else sock << "HTTP/1.1 401 Not found\n"; sock << "Server: " << GetAppName() << "\n"; sock << "Host: " << wxGetFullHostName() << "\n"; if (bFound && m_bRunning) sock << "Connection: keep-alive\n"; else sock << "Connection: close\n"; sock << "Content-Type: " << strType << "\n"; sock << "Content-Length: " << nSize << "\n"; sock << "Date: " << date.Format("%#c") << "\n"; wxDateTime dtFraUnPo = wxDateTime::Now(); const wxTimeSpan minuto(0, 1, 0); // Zero ore, Un minuto, Zero secondi dtFraUnPo += minuto; sock << "Expires: " << dtFraUnPo.Format("%#c") << "\n"; sock << "\n"; SendContent(inf, sock); } void TBaseServerApp::SoapProcessMethod(const TXmlItem& WXUNUSED(xmlMethod), TXmlItem& WXUNUSED(xmlAnswer)) { } void TBaseServerApp::ProcessSoapCommand(wxString cmd, wxSocketBase& sock) { TXmlItem xmlEnvelope; xmlEnvelope.SetTag("SOAP-ENV:Envelope"); TXmlItem& xmlBody = xmlEnvelope.AddChild("SOAP-ENV:Body"); const int soapstart = cmd.Find(" 0) { const size_t soaplen = cmd.length() - soapstart; const char* buff = (const char*)cmd; buff += soapstart; wxMemoryInputStream input(buff, soaplen); TXmlItem query; if (query.Read(input)) { const TXmlItem* pxmlBody = query.FindFirst("SOAP-ENV:Body"); if (pxmlBody != NULL) for (int m = 0; ; m++) { const TXmlItem* pxmlMethod = pxmlBody->GetChild(m); if (pxmlMethod == NULL) break; if (pxmlMethod->GetTag().StartsWith("m:")) { wxString str = pxmlMethod->GetTag(); str += "Result"; TXmlItem& xmlAnswer = xmlBody.AddChild(str); SoapProcessMethod(*pxmlMethod, xmlAnswer); } } } } const wxString strResult = xmlEnvelope.AsString(); sock << "HTTP/1.1 200 OK" << endl; sock << "Connection: keep-alive" << endl; sock << "Content-Length: " << strResult.Length() << endl; sock << "Content-Type: text/xml; charset=utf-8" << endl; sock << "Date: " << wxDateTime::Now().Format("%#c") << endl; sock << "Server: " << GetAppName() << endl; sock << "Host: " << wxGetFullHostName() << endl; sock << endl; sock << strResult; } void TBaseServerApp::ProcessHttpGet(wxString cmd, wxSocketBase& sock) { const int stop = cmd.Find(" HTTP"); wxString str = cmd.Mid(4, stop-4).Trim(); if (str == "/") str += "index.htm"; wxString strFilename = GetDocumentRoot() + str; SendFile(strFilename, sock); } void TBaseServerApp::ProcessFormCommand(wxString WXUNUSED(cmd), wxSocketBase& WXUNUSED(sock)) { } void TBaseServerApp::SendNotModifiedFile(wxSocketBase& sock) { sock << "HTTP/1.1 304 Not Modified\n"; sock << "Date: " << wxDateTime::Now().Format("%#c") << "\n"; sock << "Server: " << GetAppName() << "\n"; sock << "Connection: close\n"; } const wxChar* TBaseServerApp::GetAppName() const { // Pure virtual function return "Server"; } void TBaseServerApp::ProcessCommand(wxString cmd, wxSocketBase& outs) { if (cmd.StartsWith("POST ")) { if (cmd.Find("SOAPAction") > 0) ProcessSoapCommand(cmd, outs); else ProcessFormCommand(cmd, outs); } else if (cmd.StartsWith("GET ")) ProcessHttpGet(cmd, outs); } void TBaseServerApp::OnServerEvent(wxSocketEvent& e) { wxString s = "--- OnServerEvent: "; switch(e.GetSocketEvent()) { case wxSOCKET_CONNECTION : s.Append("wxSOCKET_CONNECTION"); break; default : s.Append("Unexpected event!"); break; } WriteLog(s); // Accept new connection if there is one in the pending // connections queue, else exit. We use Accept(FALSE) for // non-blocking accept (although if we got here, there // should ALWAYS be a pending connection). wxSocketBase* sock = m_server->Accept(FALSE); if (sock) { WriteLog("--- New client connection accepted"); } else { WriteLog("### Error: couldn't accept a new connection"); return; } sock->SetEventHandler(*this, SOCKET_ID); sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG); sock->Notify(TRUE); } struct TCommand : public wxObject { wxSocketBase * m_Sock; wxString m_Command; }; void TBaseServerApp::OnSocketEvent(wxSocketEvent& e) { wxSocketBase * sock = e.GetSocket(); switch(e.GetSocketEvent()) { case wxSOCKET_INPUT: { // We disable input events, so that the test doesn't trigger // wxSocketEvent again. TCommand * message = new TCommand; message->m_Sock = sock; const size_t BUFSIZE = 1024; wxChar buf[BUFSIZE + 1]; // Read the data memset(buf, 0, sizeof(buf)); while (sock->Read(buf, BUFSIZE).LastCount() == BUFSIZE) { message->m_Command << buf; memset(buf, 0, sizeof(buf)); } message->m_Command << buf; m_Sockets.Add(message); } break; case wxSOCKET_LOST: WriteLog("--- Socket lost."); break; default: break; } } void TBaseServerApp::OnIdle(wxIdleEvent& event) { if (m_Sockets.GetCount() > 0) { wxSocketBase& sock = *(((TCommand *) m_Sockets[0])->m_Sock); const bool valid_socket = sock.IsOk(); if (valid_socket) sock.SetNotify(wxSOCKET_LOST_FLAG); wxString & str = ((TCommand *) m_Sockets[0])->m_Command; WriteLog(str); if (CanProcessCommand(str, sock)) { const wxSocketFlags flags = sock.GetFlags(); if (valid_socket) sock.SetFlags(wxSOCKET_WAITALL); ProcessCommand(str, sock); if (valid_socket) sock.SetFlags(flags); } // Enable input events again. if (valid_socket) { sock.SetNotify(wxSOCKET_LOST_FLAG | wxSOCKET_INPUT_FLAG); sock.Notify(TRUE); } m_Sockets.RemoveAt(0); } wxApp::OnIdle(event); } const wxString& TBaseServerApp::GetConfigName() const { if (m_strIni.IsEmpty()) { wxFileName name(argv[0]); // Prendo il persorso completo del server in esecuzione name.SetName("servers"); // Trasformo il nome in servers ... name.SetExt("ini"); // ... e l'esetensione in .ini (wxString&)m_strIni = name.GetFullPath(); } return m_strIni; } void TBaseServerApp::SetConfigString(const wxChar* key, const wxChar* val, const wxChar* app) const { wxFileConfig ini("", "", GetConfigName(), "", wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_RELATIVE_PATH); wxString str; if (app == NULL || *app == '\0') app = GetAppName(); str << '/' << app; ini.SetPath(str); ini.Write(key, val); } void TBaseServerApp::SetConfigInt(const wxChar* key, int val, const wxChar* app) const { wxString str; str.Printf("%d", val); SetConfigString(key, str, app); } wxString TBaseServerApp::GetConfigString(const wxChar* key, const wxChar* def, const wxChar* app) const { wxFileConfig ini("", "", GetConfigName(), "", wxCONFIG_USE_LOCAL_FILE | wxCONFIG_USE_RELATIVE_PATH); wxString str; if (app == NULL || *app == '\0') app = GetAppName(); str << '/' << app; ini.SetPath(str); ini.Read(key, &str, def); return str; } int TBaseServerApp::GetConfigInt(const wxChar* key, int def, const wxChar* app) const { const wxString str = GetConfigString(key, "*", app); return str != "*" ? atoi(str) : def; } bool TBaseServerApp::GetConfigBool(const wxChar* key, bool def, const wxChar* app) const { bool val = def; const wxString str = GetConfigString(key, "*", app); if (str != "*") val = (str[0u] == '1') || (str[0u] == 'X') || (str[0u] == 'Y') || (str.CmpNoCase("On") == 0); return val; } int TBaseServerApp::GetDefaultPort() const { return GetConfigInt("Port", 3883); } wxString TBaseServerApp::GetLogFileName() const { return GetConfigString("LogFile"); } wxString TBaseServerApp::GetDocumentRoot() const { return GetConfigString("DocumentRoot", m_strPath); } bool TBaseServerApp::OnInit() { m_server = NULL; m_log = NULL; m_SingleInstance = new wxSingleInstanceChecker(GetAppName()); if (m_SingleInstance->IsAnotherRunning()) { delete m_SingleInstance; m_SingleInstance = NULL; return false; } wxFileName::SplitPath(argv[0], &m_strPath, NULL, NULL); if (!m_strPath.IsEmpty()) { wxSetWorkingDirectory(m_strPath); if (!wxEndsWithPathSeparator(m_strPath)) m_strPath += wxFILE_SEP_PATH; } // Create the address - defaults to localhost:0 initially wxIPV4address addr; addr.AnyAddress(); // I docs affermano che AnyAddress sia meglio di LocalHost addr.Service(GetDefaultPort()); // Create the socket m_server = new wxSocketServer(addr); // Create the Log file wxString str; str = GetLogFileName(); if (!str.IsEmpty()) m_log = new wxFileOutputStream(str); else m_log = NULL; m_nTmpCounter = 0; // We use Ok() here to see if the server is really listening str.Empty(); if (m_server->Ok()) { // Setup the event handler and subscribe to connection events m_server->SetEventHandler(*this, SERVER_ID); m_server->SetNotify(wxSOCKET_CONNECTION_FLAG); m_server->Notify(TRUE); str << GetAppName() << " listening at " << addr.Hostname() << ":" << addr.Service(); m_bRunning = true; } else { str << GetAppName() << " could not listen to port " << addr.Service(); } WriteLog(str); bool ok = m_server->Ok(); if (ok) ok = Initialization(); #ifdef WIN32 m_Tray = NULL; if (ok) { m_Tray = new TTaskBarIcon; m_Tray->Init(); } #endif return ok; } int TBaseServerApp::OnExit() { if (m_server != NULL) { Deinitialization(); delete m_SingleInstance; delete m_server; } if (m_log != NULL) { wxString str; str << GetAppName() << " shutting down."; WriteLog(str); delete m_log; } #ifdef WIN32 if (m_Tray != NULL) { delete m_Tray; m_Tray = NULL; } #endif return wxApp::OnExit(); } wxString TBaseServerApp::UnformatString(const wxString& strFormString) const { wxString strResult; for (int i = 0; strFormString[i]; i++) { switch(strFormString[i]) { case '+': strResult += ' '; break; case '%': { const wxChar c = hex2int(strFormString.Mid(i+1, 2)); strResult += c; i += 2; } break; default: strResult += strFormString[i]; break; } } return strResult; } size_t TBaseServerApp::ParseArguments(wxString args, THashTable& hashArgs) const { int uguale = args.Find('='); while (uguale > 0) { wxString name = args.Left(uguale); name.Trim(false); name.Trim(true); wxString value; const int acapo = args.Find('&'); if (acapo > uguale) value = args.Mid(uguale+1, acapo-uguale-1); else value = args.Mid(uguale+1); value.Trim(false); value.Trim(true); hashArgs.Put(name, UnformatString(value)); if (acapo > 0) { args = args.Mid(acapo+1); uguale = args.Find('='); } else uguale = -1; } return hashArgs.GetCount(); }