Drizzled Public API Documentation

json_server.cc
1 /* - mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2  * vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
3  *
4  * Copyright (C) 2011 Stewart Smith, Henrik Ingo, Mohit Srivastava
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19  */
24 #include <config.h>
25 
26 #include <unistd.h>
27 #include <fcntl.h>
28 
29 #include <drizzled/module/module.h>
30 #include <drizzled/module/context.h>
31 #include <drizzled/plugin/plugin.h>
32 #include <drizzled/plugin.h>
33 #include <drizzled/plugin/daemon.h>
34 #include <drizzled/sys_var.h>
35 #include <drizzled/gettext.h>
36 #include <drizzled/error.h>
37 #include <drizzled/session.h>
38 #include <drizzled/internal/my_sys.h>
39 #include <drizzled/internal/m_string.h>
40 #include <algorithm>
41 #include <iostream>
42 #include <boost/program_options.hpp>
44 #include <drizzled/constrained_value.h>
45 #include <evhttp.h>
46 #include <event.h>
47 #include <drizzled/execute.h>
48 #include <drizzled/sql/result_set.h>
49 
50 #include <drizzled/plugin/listen.h>
51 #include <drizzled/plugin/client.h>
52 #include <drizzled/catalog/local.h>
53 
54 #include <drizzled/pthread_globals.h>
55 #include <boost/bind.hpp>
56 
57 
58 #include <drizzled/version.h>
59 #include <plugin/json_server/json/json.h>
60 #include <plugin/json_server/db_access.h>
61 #include <plugin/json_server/http_handler.h>
62 #include <plugin/json_server/http_server.h>
63 
64 namespace po= boost::program_options;
65 using namespace drizzled;
66 using namespace std;
67 
68 namespace drizzle_plugin
69 {
70 namespace json_server
71 {
72 static const string DEFAULT_SCHEMA = "test";
73 static const string DEFAULT_TABLE = "";
74 static const string JSON_SERVER_VERSION = "0.3";
75 static const uint32_t DEFAULT_MAX_THREADS= 32;
76 static const bool DEFAULT_ALLOW_DROP_TABLE=false;
77 bool allow_drop_table;
78 string default_schema;
79 string default_table;
80 uint32_t max_threads;
81 uint32_t clone_max_threads=0;
82 bool updateSchema(Session *, set_var* var);
83 bool updateTable(Session *, set_var* var);
84 void updateMaxThreads(Session *, sql_var_t);
85 static port_constraint port;
86 
87 static in_port_t getPort(void)
88 {
89  return port.get();
90 }
91 
92 extern "C" void process_request(struct evhttp_request *req, void* );
93 extern "C" void process_root_request(struct evhttp_request *req, void* );
94 extern "C" void process_api01_version_req(struct evhttp_request *req, void* );
95 extern "C" void process_version_req(struct evhttp_request *req, void* );
96 extern "C" void process_sql_req(struct evhttp_request *req, void* );
97 extern "C" void process_json_req(struct evhttp_request *req, void* );
98 extern "C" void process_request(struct evhttp_request *req, void* )
99 {
100  struct evbuffer *buf = evbuffer_new();
101  if (buf == NULL) return;
102  evbuffer_add_printf(buf, "Requested: %s\n", evhttp_request_uri(req));
103  evhttp_send_reply(req, HTTP_OK, "OK", buf);
104 }
105 
106 extern "C" void process_root_request(struct evhttp_request *req, void* )
107 {
108  struct evbuffer *buf = evbuffer_new();
109  if (buf == NULL) return;
110 
111  std::string output;
112 
113  output.append("<html><head><title>JSON DATABASE interface demo</title></head>\n"
114  "<body>\n"
115  "<script lang=\"javascript\">\n"
116  "function to_table(obj) {\n"
117  " var str = '<table border=\"1\">';\n"
118  "for (var r=0; r< obj.length; r++) {\n"
119  " str+='<tr>';\n"
120  " for (var c=0; c < obj[r].length; c++) {\n"
121  " str+= '<td>' + obj[r][c] + '</td>';\n"
122  " }\n"
123  " str+='</tr>';\n"
124  "}\n"
125  "str+='</table>';\n"
126  "return str;\n"
127  "}\n"
128  "function to_table_from_json(obj) {\n"
129  " var str = '<table border=\"1\">';\n"
130  "for (var r=0; r< obj.length; r++) {\n"
131  " str+='<tr>';\n"
132  " str+='<td>' + obj[r]['_id'] + '</td>';\n"
133  " str+='<td>' + JSON.stringify(obj[r]['document']) + '</td>';\n"
134  " str+='</tr>';\n"
135  "}\n"
136  "str+='</table>';\n"
137  "return str;\n"
138  "}\n"
139  "function run_sql_query()\n"
140  "{\n"
141  "var url = window.location;\n"
142  "var query= document.getElementById(\"sql_query\").value;\n"
143  "var xmlHttp = new XMLHttpRequest();\n"
144  "xmlHttp.onreadystatechange = function () {\n"
145  "document.getElementById(\"responseText\").value = xmlHttp.responseText;\n"
146  "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n"
147  "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n"
148  "document.getElementById( \"resultset\").innerHTML= to_table(info.result_set);\n"
149  "}\n"
150  "};\n"
151  "xmlHttp.open(\"POST\", url + \"sql\", true);\n"
152  "xmlHttp.send(query);\n"
153  "}\n"
154  "\n\n"
155  "function run_json_query()\n"
156  "{\n"
157 //"alert('run_json_query');"
158  "var url = window.location;\n"
159  "var method= document.getElementById(\"json_method\").value;\n"
160  "var query= document.getElementById(\"json_query\").value;\n"
161  "var schema= document.getElementById(\"schema\").value;\n"
162  "var table= document.getElementById(\"table\").value;\n"
163  "var xmlHttp = new XMLHttpRequest();\n"
164  "xmlHttp.onreadystatechange = function () {\n"
165 //"alert(xmlHttp.responseText);"
166  "document.getElementById(\"responseText\").value = xmlHttp.responseText;\n"
167  "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n"
168  "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n"
169  "document.getElementById( \"resultset\").innerHTML= to_table_from_json(info.result_set);\n"
170  "}\n"
171  "};\n"
172  "if( method == \"POST\" ) {\n"
173  "xmlHttp.open(method, url + \"json?schema=\" + schema + \"&table=\" + table, true);\n"
174  "xmlHttp.send(query);\n"
175  "} else {\n"
176  "xmlHttp.open(method, url + \"json?schema=\" + schema + \"&table=\" + table + \"&query=\" + encodeURIComponent(query), true);\n"
177  "xmlHttp.send();\n"
178  "}\n"
179  "}\n"
180  "\n\n"
181  "function update_version()\n"
182  "{drizzle_version(window.location);}\n\n"
183  "function drizzle_version($url)\n"
184  "{\n"
185  "var xmlHttp = new XMLHttpRequest();\n"
186  "xmlHttp.onreadystatechange = function () {\n"
187  "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n"
188  "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n"
189  "document.getElementById( \"drizzleversion\").innerHTML= info.version;\n"
190  "}\n"
191  "};\n"
192  "xmlHttp.open(\"GET\", $url + \"version\", true);\n"
193  "xmlHttp.send(null);\n"
194  "}\n"
195  "</script>\n"
196  "<p>Drizzle server version: <a id=\"drizzleversion\"></a></p>\n"
197  "<p><textarea rows=\"3\" cols=\"80\" id=\"sql_query\">\n"
198  "SELECT * from DATA_DICTIONARY.GLOBAL_STATUS;\n"
199  "</textarea>\n"
200  "<button type=\"button\" onclick=\"run_sql_query();\">Execute SQL Query</button>\n"
201  "</p><p>\n"
202  "<textarea rows=\"8\" cols=\"80\" id=\"json_query\">\n"
203  "{\"_id\" : 1}\n"
204  "</textarea>\n"
205  "<button type=\"button\" onclick=\"run_json_query();\">Execute JSON Query</button>\n"
206  "<br />\n"
207  "<select id=\"json_method\"><option value=\"GET\">GET</option>"
208  "<option value=\"POST\">POST</option>"
209  "<option value=\"PUT\">PUT</option>"
210  "<option value=\"DELETE\">DELETE</option></select>"
211  "<script lang=\"javascript\">document.write(window.location);</script>json?schema=\n"
212  "<input type=\"text\" id=\"schema\" value=\"test\"/>"
213  "&amp;table=<input type=\"text\" id=\"table\" value=\"jsonkv\"/>\n"
214  "</p><hr />\n<div id=\"resultset\"></div>\n"
215  "<hr /><p><textarea rows=\"12\" cols=\"80\" id=\"responseText\" ></textarea></p>"
216  "<script lang=\"javascript\">update_version(); run_sql_query();</script>\n"
217  "</body></html>\n");
218 
219  evbuffer_add(buf, output.c_str(), output.length());
220  evhttp_send_reply(req, HTTP_OK, "OK", buf);
221 }
222 
223 extern "C" void process_api01_version_req(struct evhttp_request *req, void* )
224 {
225  struct evbuffer *buf = evbuffer_new();
226  if (buf == NULL) return;
227 
228  Json::Value root;
229  root["version"]= ::drizzled::version();
230 
231  Json::StyledWriter writer;
232  std::string output= writer.write(root);
233 
234  evbuffer_add(buf, output.c_str(), output.length());
235  evhttp_send_reply(req, HTTP_OK, "OK", buf);
236 }
237 
238 extern "C" void process_version_req(struct evhttp_request *req, void* )
239 {
240  struct evbuffer *buf = evbuffer_new();
241  if (buf == NULL) return;
242 
243  Json::Value root;
244  root["version"]= ::drizzled::version();
245  root["json_server_version"]=JSON_SERVER_VERSION;
246 
247  Json::StyledWriter writer;
248  std::string output= writer.write(root);
249 
250  evbuffer_add(buf, output.c_str(), output.length());
251  evhttp_send_reply(req, HTTP_OK, "OK", buf);
252 }
253 
254 extern "C" void process_sql_req(struct evhttp_request *req, void* )
255 {
256  struct evbuffer *buf = evbuffer_new();
257  if (buf == NULL) return;
258 
259  std::string input;
260  char buffer[1024];
261  int l=0;
262  do {
263  l= evbuffer_remove(req->input_buffer, buffer, 1024);
264  input.append(buffer, l);
265  }while(l);
266 
267  drizzled::Session::shared_ptr _session= drizzled::Session::make_shared(drizzled::plugin::Listen::getNullClient(),
268  drizzled::catalog::local());
269  drizzled::identifier::user::mptr user_id= identifier::User::make_shared();
270  user_id->setUser("");
271  _session->setUser(user_id);
272  _session->set_schema("test");
273 
274  drizzled::Execute execute(*(_session.get()), true);
275 
276  drizzled::sql::ResultSet result_set(1);
277 
278  /* Execute wraps the SQL to run within a transaction */
279  execute.run(input, result_set);
280  drizzled::sql::Exception exception= result_set.getException();
281 
282  drizzled::error_t err= exception.getErrorCode();
283 
284  Json::Value root;
285  root["sqlstate"]= exception.getSQLState();
286 
287  if ((err != drizzled::EE_OK) && (err != drizzled::ER_EMPTY_QUERY))
288  {
289  root["error_message"]= exception.getErrorMessage();
290  root["error_code"]= exception.getErrorCode();
291  root["schema"]= "test";
292  }
293 
294  while (result_set.next())
295  {
296  Json::Value json_row;
297  for (size_t x= 0; x < result_set.getMetaData().getColumnCount(); x++)
298  {
299  if (not result_set.isNull(x))
300  {
301  json_row[x]= result_set.getString(x);
302  }
303  }
304  root["result_set"].append(json_row);
305  }
306 
307  root["query"]= input;
308 
309  Json::StyledWriter writer;
310  std::string output= writer.write(root);
311 
312  evbuffer_add(buf, output.c_str(), output.length());
313  evhttp_send_reply(req, HTTP_OK, "OK", buf);
314 }
315 
316 
324 extern "C" void process_json_req(struct evhttp_request *req, void* )
325 {
326  Json::Value json_out;
327  Json::Value json_in;
328  std::string sql;
329  const char* schema;
330  const char* table;
331 
332  HttpHandler* handler = new HttpHandler(json_out,json_in,req);
333  if(!handler->handleRequest())
334  {
335  if(!handler->validate(default_schema,default_table,allow_drop_table))
336  {
337  json_in= handler->getInputJson();
338  schema=handler->getSchema();
339  table=handler->getTable();
340 
341  DBAccess* dbAccess = new DBAccess(json_in,json_out,req->type,schema,table);
342  dbAccess->execute();
343  json_out= dbAccess->getOutputJson();
344  delete(dbAccess);
345  }
346  else
347  {
348  json_out= handler->getOutputJson();
349  }
350  }
351  else
352  {
353  json_out= handler->getOutputJson();
354  }
355  handler->setOutputJson(json_out);
356  handler->sendResponse();
357  delete(handler);
358 }
359 
360 static void shutdown_event(int fd, short, void *arg)
361 {
362  struct event_base *base= (struct event_base *)arg;
363  event_base_loopbreak(base);
364  close(fd);
365 }
366 
367 static void run(struct event_base *base)
368 {
369  internal::my_thread_init();
370 
371  event_base_dispatch(base);
372 }
373 
374 
376 {
377 private:
378  std::vector<drizzled::thread_ptr> json_threads;
379  in_port_t _port;
380  struct evhttp *httpd;
381  struct event_base *base;
382  int wakeup_fd[2];
383  struct event wakeup_event;
384  int nfd;
385 
386 public:
387  JsonServer(in_port_t port_arg) :
388  drizzled::plugin::Daemon("json_server"),
389  _port(port_arg),
390  httpd(NULL),
391  base(NULL)
392  { }
393 
394  bool init()
395  {
396  if (pipe(wakeup_fd) < 0)
397  {
398  sql_perror("pipe");
399  return false;
400  }
401 
402  int returned_flags;
403  if ((returned_flags= fcntl(wakeup_fd[0], F_GETFL, 0)) < 0)
404  {
405  sql_perror("fcntl:F_GETFL");
406  return false;
407  }
408 
409  if (fcntl(wakeup_fd[0], F_SETFL, returned_flags | O_NONBLOCK) < 0)
410 
411  {
412  sql_perror("F_SETFL");
413  return false;
414  }
415  if ((nfd=BindSocket("0.0.0.0", getPort())) == -1)
416  {
417  sql_perror("evhttp_bind_socket()");
418  return false;
419  }
420 
421  // Create Max_thread number of threads.
422  if(not createThreads(max_threads))
423  {
424  return false;
425  }
426 
427  return true;
428  }
429 
430  bool createThreads(uint32_t num_threads)
431  {
432  for(uint32_t i =0;i<num_threads;i++)
433  {
434  if ((base= event_init()) == NULL)
435  {
436  sql_perror("event_init()");
437  return false;
438  }
439 
440  if ((httpd= evhttp_new(base)) == NULL)
441  {
442  sql_perror("evhttp_new()");
443  return false;
444  }
445 
446  if(evhttp_accept_socket(httpd,nfd))
447  {
448  sql_perror("evhttp_accept_socket()");
449  return false;
450  }
451 
452  // These URLs are available. Bind worker method to each of them.
453  evhttp_set_cb(httpd, "/", process_root_request, NULL);
454  // API 0.1
455  evhttp_set_cb(httpd, "/0.1/version", process_api01_version_req, NULL);
456  // API 0.2
457  evhttp_set_cb(httpd, "/0.2/version", process_api01_version_req, NULL);
458  // API 0.3
459  evhttp_set_cb(httpd, "/0.3/version", process_version_req, NULL);
460  evhttp_set_cb(httpd, "/0.3/sql", process_sql_req, NULL);
461  evhttp_set_cb(httpd, "/0.3/json", process_json_req, NULL);
462  // API "latest" and also available in top level
463  evhttp_set_cb(httpd, "/latest/version", process_version_req, NULL);
464  evhttp_set_cb(httpd, "/latest/sql", process_sql_req, NULL);
465  evhttp_set_cb(httpd, "/latest/json", process_json_req, NULL);
466  evhttp_set_cb(httpd, "/version", process_version_req, NULL);
467  evhttp_set_cb(httpd, "/sql", process_sql_req, NULL);
468  evhttp_set_cb(httpd, "/json", process_json_req, NULL);
469 
470 
471  event_set(&wakeup_event, wakeup_fd[0], EV_READ | EV_PERSIST, shutdown_event, base);
472  event_base_set(base, &wakeup_event);
473  if (event_add(&wakeup_event, NULL) < 0)
474  {
475  sql_perror("event_add");
476  return false;
477  }
478  drizzled::thread_ptr local_thread;
479  local_thread.reset(new boost::thread((boost::bind(&run, base))));
480  json_threads.push_back(local_thread);
481 
482  if (not json_threads[i])
483  return false;
484  }
485  return true;
486  }
487 
488  ~JsonServer()
489  {
490  // If we can't write(), we will just muddle our way through the shutdown
491  char buffer[1];
492  buffer[0]= 4;
493  if ((write(wakeup_fd[1], &buffer, 1)) == 1)
494  {
495  for(uint32_t i=0;i<max_threads;i++)
496  {
497  json_threads[i]->join();
498  }
499  evhttp_free(httpd);
500  event_base_free(base);
501  }
502  }
503 };
504 JsonServer *server=NULL;
505 
506 void updateMaxThreads(Session *, sql_var_t)
507 {
508  if (clone_max_threads < max_threads)
509  {
510  if(server->createThreads(max_threads - clone_max_threads))
511  {
512  clone_max_threads=max_threads;//success
513  }
514  else
515  {
516  //char buf[100];
517  //sprintf(buf,"json_server unable to create more threads");
518  //my_error(ER_SCRIPT,MYF(0),buf);
519  errmsg_printf(error::ERROR,_("json_server unable to create more threads"));
520  }
521  }
522  else
523  {
524  max_threads = clone_max_threads;
525  //my_error(ER_SCRIPT,MYF(0),"json_server_max_threads cannot be smaller than previous configured value");
526  errmsg_printf(error::ERROR, _("json_server_max_threadscannot be smaller than previous configured value"));//error
527  }
528 }
529 
530 
531 bool updateSchema(Session *, set_var* var)
532 {
533  if (not var->value->str_value.empty())
534  {
535  std::string new_schema(var->value->str_value.data());
536  default_schema=new_schema;
537  return false; //success
538  }
539  errmsg_printf(error::ERROR, _("json_server_schema cannot be NULL"));
540  return true; // error
541 }
542 
543 bool updateTable(Session *, set_var* var)
544 {
545  std::string new_table(var->value->str_value.data());
546  default_table=new_table;
547  return false;
548 }
549 
550 static int json_server_init(drizzled::module::Context &context)
551 {
552 
553  server = new JsonServer(port);
554  context.add(server);
555  context.registerVariable(new sys_var_constrained_value_readonly<in_port_t>("port", port));
556  context.registerVariable(new sys_var_std_string("schema", default_schema, NULL, &updateSchema));
557  context.registerVariable(new sys_var_std_string("table", default_table, NULL, &updateTable));
558  context.registerVariable(new sys_var_bool_ptr("allow_drop_table", &allow_drop_table));
559  context.registerVariable(new sys_var_uint32_t_ptr("max_threads",&max_threads,&updateMaxThreads));
560 
561  clone_max_threads=max_threads;
562 
563 
564  if (server and not server->init())
565  {
566  return -2;
567  }
568 
569  return bool(server) ? 0 : 1;
570 }
571 
572 static void init_options(drizzled::module::option_context &context)
573 {
574  context("port",
575  po::value<port_constraint>(&port)->default_value(8086),
576  _("Port number to use for connection or 0 for default (port 8086) "));
577  context("schema",
578  po::value<string>(&default_schema)->default_value(DEFAULT_SCHEMA),
579  _("Schema in use by json server"));
580  context("table",
581  po::value<string>(&default_table)->default_value(DEFAULT_TABLE),
582  _("table in use by json server"));
583  context("allow_drop_table",
584  po::value<bool>(&allow_drop_table)->default_value(DEFAULT_ALLOW_DROP_TABLE),
585  _("allow to drop table"));
586  context("max_threads",
587  po::value<uint32_t>(&max_threads)->default_value(DEFAULT_MAX_THREADS),
588  _("Maximum threads in use by json server"));
589 
590 }
591 
592 } /* namespace json_server */
593 } /* namespace drizzle_plugin */
594 
595 DRIZZLE_DECLARE_PLUGIN
596 {
597  DRIZZLE_VERSION_ID,
598  "json_server",
599  "0.3",
600  "Stewart Smith, Henrik Ingo, Mohit Srivastava",
601  N_("JSON HTTP interface"),
602  PLUGIN_LICENSE_GPL,
603  drizzle_plugin::json_server::json_server_init,
604  NULL,
605  drizzle_plugin::json_server::init_options
606 }
607 DRIZZLE_DECLARE_PLUGIN_END;
const Json::Value getOutputJson() const
Definition: db_access.h:72
const Json::Value getInputJson() const
Definition: http_handler.h:133
bool validate(string &default_schema, string &default_table, bool allow_drop_table)
Definition: http_handler.cc:76
virtual std::string write(const Value &root)
Serialize a Value in JSON format.
TODO: Rename this file - func.h is stupid.
Represents a JSON value.
Definition: value.h:149
An Proxy Wrapper around boost::program_options::variables_map.
const Json::Value getOutputJson() const
Definition: http_handler.h:125
void process_json_req(struct evhttp_request *req, void *)
Definition: json_server.cc:324
void setOutputJson(Json::Value &json_out)
Definition: http_handler.h:141
static plugin::Client * getNullClient()
Definition: listen.cc:151
Defines the event_t struct that encapsulates an event.
String str_value
Definition: item.h:107
Writes a Value in JSON format in a human friendly way.
Definition: writer.h:100
Value & append(const Value &value)
Append value to array at the end.