Drizzled Public API Documentation

json_server.cc
00001 /* - mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
00002  *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
00003  *
00004  *  Copyright (C) 2011 Stewart Smith
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program; if not, write to the Free Software
00018  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00019  */
00020 
00021 
00022 #include <config.h>
00023 
00024 #include <unistd.h>
00025 #include <fcntl.h>
00026 
00027 #include <drizzled/module/module.h>
00028 #include <drizzled/module/context.h>
00029 #include <drizzled/plugin/plugin.h>
00030 #include <drizzled/plugin.h>
00031 #include <drizzled/plugin/daemon.h>
00032 #include <drizzled/sys_var.h>
00033 #include <drizzled/gettext.h>
00034 #include <drizzled/error.h>
00035 #include <drizzled/session.h>
00036 #include <drizzled/internal/my_sys.h>
00037 #include <drizzled/internal/m_string.h>
00038 #include <algorithm>
00039 #include <iostream>
00040 #include <boost/program_options.hpp>
00041 #include <drizzled/module/option_map.h>
00042 #include <drizzled/constrained_value.h>
00043 #include <evhttp.h>
00044 #include <event.h>
00045 #include <drizzled/execute.h>
00046 #include <drizzled/sql/result_set.h>
00047 
00048 #include <drizzled/plugin/listen.h>
00049 #include <drizzled/plugin/client.h>
00050 #include <drizzled/catalog/local.h>
00051 
00052 #include <drizzled/pthread_globals.h>
00053 #include <boost/bind.hpp>
00054 
00055 
00056 #include <drizzled/version.h>
00057 #include <plugin/json_server/json/json.h>
00058 
00059 namespace po= boost::program_options;
00060 using namespace drizzled;
00061 using namespace std;
00062 
00063 namespace drizzle_plugin
00064 {
00065 namespace json_server
00066 {
00067 
00068 static port_constraint port;
00069 
00070 static in_port_t getPort(void)
00071 {
00072   return port.get();
00073 }
00074 
00075 extern "C" void process_request(struct evhttp_request *req, void* );
00076 extern "C" void process_root_request(struct evhttp_request *req, void* );
00077 extern "C" void process_api01_version_req(struct evhttp_request *req, void* );
00078 extern "C" void process_api01_sql_req(struct evhttp_request *req, void* );
00079 
00080 extern "C" void process_request(struct evhttp_request *req, void* )
00081 {
00082   struct evbuffer *buf = evbuffer_new();
00083   if (buf == NULL) return;
00084   evbuffer_add_printf(buf, "Requested: %s\n", evhttp_request_uri(req));
00085   evhttp_send_reply(req, HTTP_OK, "OK", buf);
00086 }
00087 
00088 extern "C" void process_root_request(struct evhttp_request *req, void* )
00089 {
00090   struct evbuffer *buf = evbuffer_new();
00091   if (buf == NULL) return;
00092 
00093   std::string output;
00094 
00095   output.append("<html><head><title>JSON DATABASE interface demo</title></head>"
00096                 "<body>"
00097                 "<script lang=\"javascript\">"
00098                 "function to_table(obj) {"
00099                 " var str = '<table>';"
00100                 "for (var r=0; r< obj.length; r++) {"
00101                 " str+='<tr>';"
00102                 "  for (var c=0; c < obj[r].length; c++) {"
00103                 "    str+= '<td>' + obj[r][c] + '</td>';"
00104                 "  }"
00105                 " str+='</tr>';"
00106                 "}"
00107                 "str+='</table>';"
00108                 "return str;"
00109                 "}"
00110                 "function run_query()\n"
00111                 "{"
00112                 "var url = document.getElementById(\"baseurl\").innerHTML;\n"
00113                 "var query= document.getElementById(\"query\").value;\n"
00114                 "var xmlHttp = new XMLHttpRequest();\n"
00115                 "xmlHttp.onreadystatechange = function () {\n"
00116                 "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n"
00117                 "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n"
00118                 "document.getElementById( \"resultset\").innerHTML= to_table(info.result_set);\n"
00119                 "}\n"
00120                 "};\n"
00121                 "xmlHttp.open(\"POST\", url + \"/0.1/sql\", true);"
00122                 "xmlHttp.send(query);"
00123                 "}"
00124                 "\n\n"
00125                 "function update_version()\n"
00126                 "{drizzle_version(document.getElementById(\"baseurl\").innerHTML);}\n\n"
00127                 "function drizzle_version($url)"
00128                 "{"
00129                 "var xmlHttp = new XMLHttpRequest();\n"
00130                 "xmlHttp.onreadystatechange = function () {\n"
00131                 "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n"
00132                 "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n"
00133                 "document.getElementById( \"drizzleversion\").innerHTML= info.version;\n"
00134                 "}\n"
00135                 "};\n"
00136                 "xmlHttp.open(\"GET\", $url + \"/0.1/version\", true);"
00137                 "xmlHttp.send(null);"
00138                 "}"
00139                 "</script>"
00140                 "<p>Drizzle Server at: <a id=\"baseurl\">http://localhost:8765</a></p>"
00141                 "<p>Drizzle server version: <a id=\"drizzleversion\"></a></p>"
00142                 "<p><textarea rows=\"3\" cols=\"40\" id=\"query\">"
00143                 "SELECT * from DATA_DICTIONARY.GLOBAL_STATUS;"
00144                 "</textarea>"
00145                 "<button type=\"button\" onclick=\"run_query();\">Execute Query</button>"
00146                 "<div id=\"resultset\"/>"
00147                 "<script lang=\"javascript\">update_version(); run_query();</script>"
00148                 "</body></html>");
00149 
00150   evbuffer_add(buf, output.c_str(), output.length());
00151   evhttp_send_reply(req, HTTP_OK, "OK", buf);
00152 }
00153 
00154 extern "C" void process_api01_version_req(struct evhttp_request *req, void* )
00155 {
00156   struct evbuffer *buf = evbuffer_new();
00157   if (buf == NULL) return;
00158 
00159   Json::Value root;
00160   root["version"]= ::drizzled::version();
00161 
00162   Json::StyledWriter writer;
00163   std::string output= writer.write(root);
00164 
00165   evbuffer_add(buf, output.c_str(), output.length());
00166   evhttp_send_reply(req, HTTP_OK, "OK", buf);
00167 }
00168 
00169 extern "C" void process_api01_sql_req(struct evhttp_request *req, void* )
00170 {
00171   struct evbuffer *buf = evbuffer_new();
00172   if (buf == NULL) return;
00173 
00174   std::string input;
00175   char buffer[1024];
00176   int l=0;
00177   do {
00178     l= evbuffer_remove(req->input_buffer, buffer, 1024);
00179     input.append(buffer, l);
00180   }while(l);
00181 
00182   drizzled::Session::shared_ptr _session= drizzled::Session::make_shared(drizzled::plugin::Listen::getNullClient(),
00183                                            drizzled::catalog::local());
00184   drizzled::identifier::user::mptr user_id= identifier::User::make_shared();
00185   user_id->setUser("");
00186   _session->setUser(user_id);
00187   _session->set_schema("test");
00188 
00189   drizzled::Execute execute(*(_session.get()), true);
00190 
00191   drizzled::sql::ResultSet result_set(1);
00192 
00193   /* Execute wraps the SQL to run within a transaction */
00194   execute.run(input, result_set);
00195   drizzled::sql::Exception exception= result_set.getException();
00196 
00197   drizzled::error_t err= exception.getErrorCode();
00198 
00199   Json::Value root;
00200   root["sqlstate"]= exception.getSQLState();
00201 
00202   if ((err != drizzled::EE_OK) && (err != drizzled::ER_EMPTY_QUERY))
00203   {
00204     root["error_message"]= exception.getErrorMessage();
00205     root["error_code"]= exception.getErrorCode();
00206   }
00207 
00208   while (result_set.next())
00209   {
00210     Json::Value json_row;
00211     for (size_t x= 0; x < result_set.getMetaData().getColumnCount(); x++)
00212     {
00213       if (not result_set.isNull(x))
00214       {
00215         json_row[x]= result_set.getString(x);
00216       }
00217     }
00218     root["result_set"].append(json_row);
00219   }
00220 
00221   root["query"]= input;
00222 
00223   Json::StyledWriter writer;
00224   std::string output= writer.write(root);
00225 
00226   evbuffer_add(buf, output.c_str(), output.length());
00227   evhttp_send_reply(req, HTTP_OK, "OK", buf);
00228 }
00229 
00230 static void shutdown_event(int fd, short, void *arg)
00231 {
00232   struct event_base *base= (struct event_base *)arg;
00233   event_base_loopbreak(base);
00234   close(fd);
00235 }
00236 
00237 
00238 static void run(struct event_base *base)
00239 {
00240   internal::my_thread_init();
00241 
00242   event_base_dispatch(base);
00243 }
00244 
00245 
00246 class JsonServer : public drizzled::plugin::Daemon
00247 {
00248 private:
00249   drizzled::thread_ptr json_thread;
00250   in_port_t _port;
00251   struct evhttp *httpd;
00252   struct event_base *base;
00253   int wakeup_fd[2];
00254   struct event wakeup_event;
00255 
00256 public:
00257   JsonServer(in_port_t port_arg) :
00258     drizzled::plugin::Daemon("JSON Server"),
00259     _port(port_arg),
00260     httpd(NULL),
00261     base(NULL)
00262   { }
00263 
00264   bool init()
00265   {
00266     if (pipe(wakeup_fd) < 0)
00267     {
00268       sql_perror("pipe");
00269       return false;
00270     }
00271 
00272     int returned_flags;
00273     if ((returned_flags= fcntl(wakeup_fd[0], F_GETFL, 0)) < 0)
00274     {
00275       sql_perror("fcntl:F_GETFL");
00276       return false;
00277     }
00278 
00279     if (fcntl(wakeup_fd[0], F_SETFL, returned_flags | O_NONBLOCK) < 0)
00280 
00281     {
00282       sql_perror("F_SETFL");
00283       return false;
00284     }
00285 
00286     if ((base= event_init()) == NULL)
00287     {
00288       sql_perror("event_init()");
00289       return false;
00290     }
00291 
00292     if ((httpd= evhttp_new(base)) == NULL)
00293     {
00294       sql_perror("evhttp_new()");
00295       return false;
00296     }
00297 
00298 
00299     if ((evhttp_bind_socket(httpd, "0.0.0.0", getPort())) == -1)
00300     {
00301       sql_perror("evhttp_bind_socket()");
00302       return false;
00303     }
00304 
00305     evhttp_set_cb(httpd, "/", process_root_request, NULL);
00306     evhttp_set_cb(httpd, "/0.1/version", process_api01_version_req, NULL);
00307     evhttp_set_cb(httpd, "/0.1/sql", process_api01_sql_req, NULL);
00308     evhttp_set_gencb(httpd, process_request, NULL);
00309 
00310     event_set(&wakeup_event, wakeup_fd[0], EV_READ | EV_PERSIST, shutdown_event, base);
00311     event_base_set(base, &wakeup_event);
00312     if (event_add(&wakeup_event, NULL) < 0)
00313     {
00314       sql_perror("event_add");
00315       return false;
00316     }
00317 
00318     json_thread.reset(new boost::thread((boost::bind(&run, base))));
00319 
00320     if (not json_thread)
00321       return false;
00322 
00323     return true;
00324   }
00325 
00326   ~JsonServer()
00327   {
00328     // If we can't write(), we will just muddle our way through the shutdown
00329     char buffer[1];
00330     buffer[0]= 4;
00331     if ((write(wakeup_fd[1], &buffer, 1)) == 1)
00332     {
00333       json_thread->join();
00334       evhttp_free(httpd);
00335       event_base_free(base);
00336     }
00337   }
00338 };
00339 
00340 static int json_server_init(drizzled::module::Context &context)
00341 {
00342   context.registerVariable(new sys_var_constrained_value_readonly<in_port_t>("port", port));
00343 
00344   JsonServer *server;
00345   context.add(server= new JsonServer(port));
00346 
00347   if (server and not server->init())
00348   {
00349     return -2;
00350   }
00351 
00352   return bool(server) ? 0 : 1;
00353 }
00354 
00355 static void init_options(drizzled::module::option_context &context)
00356 {
00357   context("port",
00358           po::value<port_constraint>(&port)->default_value(8086),
00359           _("Port number to use for connection or 0 for default (port 8086) "));
00360 }
00361 
00362 } /* namespace json_server */
00363 } /* namespace drizzle_plugin */
00364 
00365 DRIZZLE_DECLARE_PLUGIN
00366 {
00367   DRIZZLE_VERSION_ID,
00368   "json-server",
00369   "0.1",
00370   "Stewart Smith",
00371   "JSON HTTP interface",
00372   PLUGIN_LICENSE_GPL,
00373   drizzle_plugin::json_server::json_server_init,             /* Plugin Init */
00374   NULL, /* depends */
00375   drizzle_plugin::json_server::init_options    /* config options */
00376 }
00377 DRIZZLE_DECLARE_PLUGIN_END;