/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * server.cc * * Wed Aug 22 12:16:03 CEST 2007 * Copyright 2007 Bent Bisballe Nyeng * deva@aasimon.org ****************************************************************************/ /* * This file is part of Pracro. * * Pracro 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. * * Pracro 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 Pracro; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "server.h" #include "tcpsocket.h" #include #include // For fork #include #include #include #include #include "configuration.h" #include "transaction.h" #include "transactionparser.h" #include "templateparser.h" #include "macroparser.h" #include "queryhandler.h" #include "queryhandlerpracro.h" #include "queryhandlerpentominos.h" #include "queryparser.h" #include "luaquerymapper.h" #include "database.h" #include "widgetgenerator.h" #include "resumeparser.h" #include "journal_commit.h" #include "xml_encode_decode.h" #include "macrolist.h" #include "templatelist.h" #include "versionstr.h" #include "mutex.h" #include "log.h" typedef long long unsigned int sessionid_t; typedef struct { JournalWriter *journalwriter; } session_t; struct conn_t { Database *db; Mutex mutex; std::map sessions; }; static std::string error_box(std::string message) { std::string errorbox = "\n" "\n" " " + message + "\n" "\n"; return errorbox; } class NotFoundException : public Exception { public: NotFoundException(Request &r) : Exception("Macro " + r.macro + " not found in template " + r.templ) {} }; static std::string handleCommits(Transaction *transaction, Database &db, JournalWriter &journalwriter, MacroList ¯olist, TemplateList &templatelist) { std::string answer; Commits::iterator i = transaction->commits.begin(); while(i != transaction->commits.end()) { Commit &commit = *i; MacroParser mp(macrolist.getLatestVersion(commit.macro)); mp.parse(); Macro *macro = mp.getMacro(); std::string resume = resume_parser(macro->resume, commit); commit.fields["journal.resume"] = resume; db.commitTransaction(transaction->user, transaction->cpr, *macro, commit.fields); if(resume != "") { TemplateParser tp(templatelist.getLatestVersion(commit.templ)); tp.parse(); Template *templ = tp.getTemplate(); journalwriter.addEntry(*transaction, commit, resume, templ); } i++; } return answer; } static std::string handleRequest(Transaction *transaction, TCPSocket &pentominos_socket, Database &db, MacroList ¯olist, TemplateList &templatelist) { std::string answer; Requests::iterator i = transaction->requests.begin(); while(i != transaction->requests.end()) { Request &request = *i; PRACRO_DEBUG(server, "Handling request - macro: %s, template: %s\n", request.macro.c_str(), request.templ.c_str()); // Read and parse the template file. TemplateParser tp(templatelist.getLatestVersion(request.templ)); tp.parse(); Template *templ = tp.getTemplate(); answer += " \n"; i++; } return answer; } static std::string handleTransaction(Transaction *transaction, TCPSocket &pentominos_socket, Database &db, JournalWriter &journalwriter, MacroList ¯olist, TemplateList &templatelist) { std::string answer; answer += "\n"; answer += "\n"; try { answer += handleCommits(transaction, db, journalwriter, macrolist, templatelist); } catch( std::exception &e ) { PRACRO_ERR(server, "Commit error: %s\n", e.what()); return error_box(xml_encode(e.what())); } try { answer += handleRequest(transaction, pentominos_socket, db, macrolist, templatelist); } catch( std::exception &e ) { PRACRO_ERR(server, "Request error: %s\n", e.what()); return error_box(xml_encode(e.what())); } answer += "\n"; PRACRO_DEBUG(server, "Done handling transaction\n"); PRACRO_DEBUG(serverxml, "%s\n", answer.c_str()); return answer; } static std::string handleConnection(const char *buf, size_t size, struct conn_t *conn, sessionid_t sid, bool commitsession) { /* if(size == 0) return error_box(xml_encode("Empty document received.")); */ TCPSocket pentominos_socket; #ifndef WITHOUT_PENTOMINOS pentominos_socket.connect(Conf::pentominos_addr, Conf::pentominos_port); #endif/*WITHOUT_PENTOMINOS*/ JournalWriter *journalwriter = NULL; conn->mutex.lock(); if(conn->sessions.find(sid) == conn->sessions.end()) { conn->sessions[sid].journalwriter = new JournalWriter(Conf::journal_commit_addr.c_str(), Conf::journal_commit_port); } journalwriter = conn->sessions[sid].journalwriter; conn->mutex.unlock(); MacroList macrolist(Conf::xml_basedir + "/macros"); TemplateList templatelist(Conf::xml_basedir + "/templates"); Transaction transaction; TransactionParser parser(&transaction); PRACRO_DEBUG(server, "Read %d bytes from network\n", size); std::string res; if(size) { if(parser.parse(buf, size)) { PRACRO_DEBUG(server, "Got complete XML document, %d bytes in current buffer.\n", size); res = handleTransaction(&transaction, pentominos_socket, *conn->db, *journalwriter, macrolist, templatelist); } else { PRACRO_ERR(server, "Failed to parse data!\n"); res = error_box(xml_encode("XML Parse error.")); } } if(commitsession) { journalwriter->commit(); delete journalwriter; conn->mutex.lock(); conn->sessions.erase(sid); conn->mutex.unlock(); } return res; } static int handle_request(void *cls, struct MHD_Connection *con, const char *url, const char *method, const char *version, const char *data, unsigned int *data_size, void **ptr) { struct conn_t *conn = (struct conn_t*)cls; PRACRO_DEBUG(httpd, "handle_request(url=\"%s\", method=\"%s\"," " version=\"%s\", data_size=\"%d\")\n", url, method, version, *data_size); sessionid_t sessionid; bool commitsession = false; bool sid_ok = true; const char *sessionids = MHD_lookup_connection_value(con, MHD_HEADER_KIND, "SessionID"); if(sessionids == NULL) { sessionid = 42;//newSessionID(conn); } else { sessionid = atoll(sessionids); conn->mutex.lock(); sid_ok = conn->sessions.find(sessionid) != conn->sessions.end(); conn->mutex.unlock(); } PRACRO_DEBUG(httpd, "SessionID: %llu\n", sessionid); const char *session_commit = MHD_lookup_connection_value(con, MHD_HEADER_KIND, "SessionCommit"); if(session_commit) { PRACRO_DEBUG(httpd, "COMMIT: sessionid %llu\n", sessionid); commitsession = true; } std::string reply; if(sid_ok) { reply = handleConnection(data, *data_size, conn, sessionid, commitsession); } else { PRACRO_ERR(httpd, "No such sessionid %llu\n", sessionid); reply = error_box("No such session ID!"); } struct MHD_Response *rsp; rsp = MHD_create_response_from_data(reply.length(), (char*)reply.c_str(), MHD_NO, MHD_YES); MHD_add_response_header(rsp, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain; charset=UTF-8"); char idbuf[32]; snprintf(idbuf, sizeof(idbuf), "%llu", sessionid); MHD_add_response_header(rsp, "SessionID", idbuf); int ret = MHD_queue_response(con, MHD_HTTP_OK, rsp); MHD_destroy_response(rsp); *data_size = 0; return ret; } static void httpderr(void *arg, const char *fmt, va_list ap) { PRACRO_ERR_VA(server, fmt, ap); } #define CERT "\ -----BEGIN CERTIFICATE-----\n\ MIICFTCCAX6gAwIBAgIBAjANBgkqhkiG9w0BAQUFADBVMRswGQYDVQQKExJBcGFj\n\ aGUgSFRUUCBTZXJ2ZXIxIjAgBgNVBAsTGUZvciB0ZXN0aW5nIHB1cnBvc2VzIG9u\n\ bHkxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0wNzA2MjEwODE4MzZaFw0wODA2MjAw\n\ ODE4MzZaMEwxGzAZBgNVBAoTEkFwYWNoZSBIVFRQIFNlcnZlcjEZMBcGA1UECxMQ\n\ VGVzdCBDZXJ0aWZpY2F0ZTESMBAGA1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3\n\ DQEBAQUAA4GNADCBiQKBgQDWTACKSoxd5cL06w7RtPIhFqY1l3UE/aRGmPmh8gEo\n\ w3zNf+gWxco2yjQgBTQhGww1ybOsAUtXPIsUOSFAGvPUKJZf8ibZMiJEzl2919uz\n\ IcV9+cUm7k3jFPQx4ALQEalbV++o/lfT5lhgsSiH1t1eln2omVrGCjI/1HeYrw7X\n\ owIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALVFzprK6rYkWVZZZwq85w2lCYJpEl9a\n\ 66IMzIwNNRfyZMoc9D9PSwsXKYfYOg1RpMt7RhWT/bpggGlsFqctsAgJSv8Ol5Cz\n\ DqTXhpV+8WOG6l4xDYZz3U3ajiu2jth2+aaMuWKy9Wkr8bzHGDufltToLalucne2\n\ npM7yCJ83Ana\n\ -----END CERTIFICATE-----" #define KEY "\ -----BEGIN RSA PRIVATE KEY-----\n\ MIICXAIBAAKBgQDWTACKSoxd5cL06w7RtPIhFqY1l3UE/aRGmPmh8gEow3zNf+gW\n\ xco2yjQgBTQhGww1ybOsAUtXPIsUOSFAGvPUKJZf8ibZMiJEzl2919uzIcV9+cUm\n\ 7k3jFPQx4ALQEalbV++o/lfT5lhgsSiH1t1eln2omVrGCjI/1HeYrw7XowIDAQAB\n\ AoGANUXHjJljs6P+hyw4DuHQn3El+ISiTo9PW02EIUIsD5opWFzHsYGR93Tk6GDi\n\ yKgUrPprdAMOW61tVaWuImWQ32R2xyrJogjGYo9XE2xAej9N37jM0AGBtn/vd4Dr\n\ LsYfpjNaM3gqIChD73iYfO+CrNbdLqTxIdG53g/u05GJ4cECQQD0vMm5+a8N82Jb\n\ oHJgE2jb83WqaYBHe0O03ujtiq3+hPZHoVV3iJWmA/aMlgdtunkJT3PdEsVfQNkH\n\ fvzR9JhbAkEA4CiZRk5Gcz7cEqyogDTMQYtmrE8hbgofISLuz1rpTEzd8hFAcerU\n\ nuwFIT3go3hO7oIHMlKU1H5iT1BsFvegWQJBAOSa6A+5A+STIKAX+l52Iu+5tYKN\n\ 885RfMgZpBgm/yoMxwPX1r7GLYsajpV5mszLbz3cIo0xeH3mVBOlccEoqZsCQECP\n\ 8PWq/eebp09Jo46pplsKh5wBfqNvDuBAa4AVszRiv1pFVcZ52JudZyzX4aezsyhH\n\ E0OPPYamkDI/+6Hx2KECQHF9xV1XatyXuFmfRAInK2BtfGY5UIvJaLxVD3Z1+i6q\n\ /enz7/wUwvC6G4FSWNMYgAYJOfwZ3BerdkqcRNxyR/Q=\n\ -----END RSA PRIVATE KEY-----" extern bool pracro_is_running; void server() { srand(time(NULL)); bool forceshutdown = false; port_t port = Conf::server_port; PRACRO_DEBUG(server, "Server running on port %d.\n", port); struct conn_t conn; conn.db = new Database(Conf::database_backend, Conf::database_addr, "", Conf::database_user, Conf::database_passwd, ""); struct MHD_Daemon *d; d = MHD_start_daemon(MHD_USE_DEBUG | MHD_USE_SELECT_INTERNALLY // | MHD_USE_PEDANTIC_CHECKS // | MHD_USE_SSL , port, NULL, NULL, handle_request, &conn, MHD_OPTION_NOTIFY_COMPLETED, NULL, NULL, // MHD_OPTION_CONNECTION_LIMIT, 42, MHD_OPTION_HTTPS_MEM_KEY, KEY, MHD_OPTION_HTTPS_MEM_CERT, CERT, //MHD_OPTION_CONNECTION_TIMEOUT, 0, MHD_OPTION_EXTERNAL_LOGGER, httpderr, NULL, MHD_OPTION_END); if(!d) { PRACRO_ERR(server, "Failed to initialise MHD_start_daemon!\n"); return; } again: while(pracro_is_running) sleep(1); if(!forceshutdown && conn.sessions.size() != 0) { char errbuf[128]; snprintf(errbuf, sizeof(errbuf), "There are %d live sessions." " Kill again to force shutdown.\n", conn.sessions.size()); PRACRO_ERR_LOG(server, "%s", errbuf); log(errbuf); pracro_is_running = true; forceshutdown = true; goto again; } delete conn.db; MHD_stop_daemon(d); PRACRO_DEBUG(server, "Server gracefully shut down.\n"); } #if 0 //#define NON_FORKING #include extern bool pracro_is_running; void server() { port_t port = Conf::server_port; TCPSocket *socket = NULL; try { socket = new TCPSocket("Listen socket"); socket->listen(port); } catch (Exception &e) { PRACRO_ERR_LOG(server, "Error in listen:\n%s\n", e.what()); delete socket; socket = NULL; return; } while(pracro_is_running && socket->connected()) { { // Reload if new port is assigned. int old_port = port; port = Conf::server_port; if(port != old_port) { // Start listening on the new port delete socket; socket = new TCPSocket("Listen socket (reloaded)"); socket->listen(port); } } TCPSocket *child = socket->accept(); if(child) { #ifndef NON_FORKING switch(fork()) { case -1: // error PRACRO_ERR_LOG(server, "Could not fork: %s\n", strerror(errno)); break; case 0: // child delete socket; #endif/*NON_FORKING*/ handleConnection(child); delete child; #ifndef NON_FORKING return; default: // parent delete child; break; } #endif/*NON_FORKING*/ } } //socket->shutdown(); delete socket; PRACRO_DEBUG(server, "Server gracefully shut down.\n"); } #endif//0 #ifdef TEST_SERVER #include #include bool pracro_is_running = true; char request[] = "\n" "\n" " \n" "\n"; int main() { Conf::xml_basedir = "../xml/"; Conf::server_port = 32100; // Make sure wo don't interrupt an already running server. Conf::database_backend = "testdb"; pid_t pid = fork(); switch(pid) { case -1: // error perror("fork() failed!\n"); return 1; case 0: // child try { server(); } catch(Exception &e) { printf(e.what()); return 1; } return 0; default: // parent try { // sleep(1); // Make sure the server is started. TCPSocket socket; socket.connect("localhost", Conf::server_port); socket.write(request); // sleep(1); // Make sure the server has handled the request. char buf[32]; memset(buf, 0, sizeof(buf)); // while(socket.read(buf, 31, 1000)) { while(socket.read(buf, 31, 1000000)) { printf(buf); fflush(stdout); memset(buf, 0, sizeof(buf)); } } catch(Exception &e) { printf(e.what()); kill(pid, SIGKILL); // Kill the server again. return 1; } kill(pid, SIGKILL); // Kill the server again. return 0; } return 1; } #endif/*TEST_SERVER*/