~drizzle-trunk/drizzle/development

2283.4.1 by Stewart Smith
very basic HTTP server
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
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
 */
20
21
22
#include <config.h>
2363.1.1 by Brian Aker
Fix memory leak in temporal and json server.
23
24
#include <unistd.h>
25
#include <fcntl.h>
26
2283.4.1 by Stewart Smith
very basic HTTP server
27
#include <drizzled/module/module.h>
28
#include <drizzled/module/context.h>
29
#include <drizzled/plugin/plugin.h>
30
#include <drizzled/plugin.h>
2363.1.1 by Brian Aker
Fix memory leak in temporal and json server.
31
#include <drizzled/plugin/daemon.h>
2283.4.1 by Stewart Smith
very basic HTTP server
32
#include <drizzled/sys_var.h>
33
#include <drizzled/gettext.h>
34
#include <drizzled/error.h>
35
#include <drizzled/session.h>
36
#include <drizzled/internal/my_sys.h>
37
#include <drizzled/internal/m_string.h>
38
#include <algorithm>
39
#include <iostream>
40
#include <boost/program_options.hpp>
41
#include <drizzled/module/option_map.h>
42
#include <drizzled/constrained_value.h>
43
#include <evhttp.h>
2283.4.6 by Stewart Smith
add a simple SQL interface to json_server plugin. /0.1/sql takes a HTTP POST SQL command and returns the result set.
44
#include <event.h>
45
#include <drizzled/execute.h>
46
#include <drizzled/sql/result_set.h>
47
48
#include <drizzled/plugin/listen.h>
49
#include <drizzled/plugin/client.h>
50
#include <drizzled/catalog/local.h>
2283.4.1 by Stewart Smith
very basic HTTP server
51
2363.1.1 by Brian Aker
Fix memory leak in temporal and json server.
52
#include <drizzled/pthread_globals.h>
53
#include <boost/bind.hpp>
54
55
2283.4.3 by Stewart Smith
add JSON library to json_server, fix up for building in our tree, and add simple /0.1/version JSON response as well as latest_api_version snippet to /
56
#include <drizzled/version.h>
57
#include <plugin/json_server/json/json.h>
58
2283.4.1 by Stewart Smith
very basic HTTP server
59
namespace po= boost::program_options;
60
using namespace drizzled;
61
using namespace std;
62
63
namespace drizzle_plugin
64
{
65
namespace json_server
66
{
67
68
static port_constraint port;
69
70
static in_port_t getPort(void)
71
{
72
  return port.get();
73
}
74
75
extern "C" void process_request(struct evhttp_request *req, void* );
76
extern "C" void process_root_request(struct evhttp_request *req, void* );
2283.4.3 by Stewart Smith
add JSON library to json_server, fix up for building in our tree, and add simple /0.1/version JSON response as well as latest_api_version snippet to /
77
extern "C" void process_api01_version_req(struct evhttp_request *req, void* );
2283.4.6 by Stewart Smith
add a simple SQL interface to json_server plugin. /0.1/sql takes a HTTP POST SQL command and returns the result set.
78
extern "C" void process_api01_sql_req(struct evhttp_request *req, void* );
2283.4.1 by Stewart Smith
very basic HTTP server
79
80
extern "C" void process_request(struct evhttp_request *req, void* )
81
{
82
  struct evbuffer *buf = evbuffer_new();
83
  if (buf == NULL) return;
84
  evbuffer_add_printf(buf, "Requested: %s\n", evhttp_request_uri(req));
85
  evhttp_send_reply(req, HTTP_OK, "OK", buf);
86
}
87
88
extern "C" void process_root_request(struct evhttp_request *req, void* )
89
{
90
  struct evbuffer *buf = evbuffer_new();
91
  if (buf == NULL) return;
2283.4.3 by Stewart Smith
add JSON library to json_server, fix up for building in our tree, and add simple /0.1/version JSON response as well as latest_api_version snippet to /
92
2283.4.7 by Stewart Smith
it's way more fun if the json_server serves out a AJAX web app that lets you execute SQL queries that get displaed as a HTML table.
93
  std::string output;
2283.4.3 by Stewart Smith
add JSON library to json_server, fix up for building in our tree, and add simple /0.1/version JSON response as well as latest_api_version snippet to /
94
2283.4.7 by Stewart Smith
it's way more fun if the json_server serves out a AJAX web app that lets you execute SQL queries that get displaed as a HTML table.
95
  output.append("<html><head><title>JSON DATABASE interface demo</title></head>"
96
                "<body>"
97
                "<script lang=\"javascript\">"
98
                "function to_table(obj) {"
99
                " var str = '<table>';"
100
                "for (var r=0; r< obj.length; r++) {"
101
                " str+='<tr>';"
102
                "  for (var c=0; c < obj[r].length; c++) {"
103
                "    str+= '<td>' + obj[r][c] + '</td>';"
104
                "  }"
105
                " str+='</tr>';"
106
                "}"
107
                "str+='</table>';"
108
                "return str;"
109
                "}"
110
                "function run_query()\n"
111
                "{"
112
                "var url = document.getElementById(\"baseurl\").innerHTML;\n"
2283.4.8 by Stewart Smith
fix a bug in json_server so that we execute the query in the text box instead of the underlaying innerHTML
113
                "var query= document.getElementById(\"query\").value;\n"
2283.4.7 by Stewart Smith
it's way more fun if the json_server serves out a AJAX web app that lets you execute SQL queries that get displaed as a HTML table.
114
                "var xmlHttp = new XMLHttpRequest();\n"
115
                "xmlHttp.onreadystatechange = function () {\n"
116
                "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n"
117
                "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n"
118
                "document.getElementById( \"resultset\").innerHTML= to_table(info.result_set);\n"
119
                "}\n"
120
                "};\n"
121
                "xmlHttp.open(\"POST\", url + \"/0.1/sql\", true);"
122
                "xmlHttp.send(query);"
123
                "}"
124
                "\n\n"
125
                "function update_version()\n"
126
                "{drizzle_version(document.getElementById(\"baseurl\").innerHTML);}\n\n"
127
                "function drizzle_version($url)"
128
                "{"
129
                "var xmlHttp = new XMLHttpRequest();\n"
130
                "xmlHttp.onreadystatechange = function () {\n"
131
                "if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {\n"
132
                "var info = eval ( \"(\" + xmlHttp.responseText + \")\" );\n"
133
                "document.getElementById( \"drizzleversion\").innerHTML= info.version;\n"
134
                "}\n"
135
                "};\n"
136
                "xmlHttp.open(\"GET\", $url + \"/0.1/version\", true);"
137
                "xmlHttp.send(null);"
138
                "}"
139
                "</script>"
140
                "<p>Drizzle Server at: <a id=\"baseurl\">http://localhost:8765</a></p>"
141
                "<p>Drizzle server version: <a id=\"drizzleversion\"></a></p>"
142
                "<p><textarea rows=\"3\" cols=\"40\" id=\"query\">"
143
                "SELECT * from DATA_DICTIONARY.GLOBAL_STATUS;"
144
                "</textarea>"
145
                "<button type=\"button\" onclick=\"run_query();\">Execute Query</button>"
146
                "<div id=\"resultset\"/>"
147
                "<script lang=\"javascript\">update_version(); run_query();</script>"
148
                "</body></html>");
2283.4.3 by Stewart Smith
add JSON library to json_server, fix up for building in our tree, and add simple /0.1/version JSON response as well as latest_api_version snippet to /
149
150
  evbuffer_add(buf, output.c_str(), output.length());
151
  evhttp_send_reply(req, HTTP_OK, "OK", buf);
152
}
153
154
extern "C" void process_api01_version_req(struct evhttp_request *req, void* )
155
{
156
  struct evbuffer *buf = evbuffer_new();
157
  if (buf == NULL) return;
158
159
  Json::Value root;
160
  root["version"]= ::drizzled::version();
161
162
  Json::StyledWriter writer;
163
  std::string output= writer.write(root);
164
165
  evbuffer_add(buf, output.c_str(), output.length());
2283.4.1 by Stewart Smith
very basic HTTP server
166
  evhttp_send_reply(req, HTTP_OK, "OK", buf);
167
}
168
2283.4.6 by Stewart Smith
add a simple SQL interface to json_server plugin. /0.1/sql takes a HTTP POST SQL command and returns the result set.
169
extern "C" void process_api01_sql_req(struct evhttp_request *req, void* )
170
{
171
  struct evbuffer *buf = evbuffer_new();
172
  if (buf == NULL) return;
173
174
  std::string input;
175
  char buffer[1024];
176
  int l=0;
177
  do {
178
    l= evbuffer_remove(req->input_buffer, buffer, 1024);
179
    input.append(buffer, l);
180
  }while(l);
181
182
  drizzled::Session::shared_ptr _session= drizzled::Session::make_shared(drizzled::plugin::Listen::getNullClient(),
183
                                           drizzled::catalog::local());
184
  drizzled::identifier::user::mptr user_id= identifier::User::make_shared();
185
  user_id->setUser("");
186
  _session->setUser(user_id);
2385.1.7 by Olaf van der Spek
Rename set_db to set_schema
187
  _session->set_schema("test");
2283.4.6 by Stewart Smith
add a simple SQL interface to json_server plugin. /0.1/sql takes a HTTP POST SQL command and returns the result set.
188
189
  drizzled::Execute execute(*(_session.get()), true);
190
191
  drizzled::sql::ResultSet result_set(1);
192
193
  /* Execute wraps the SQL to run within a transaction */
194
  execute.run(input, result_set);
195
  drizzled::sql::Exception exception= result_set.getException();
196
197
  drizzled::error_t err= exception.getErrorCode();
198
199
  Json::Value root;
200
  root["sqlstate"]= exception.getSQLState();
201
202
  if ((err != drizzled::EE_OK) && (err != drizzled::ER_EMPTY_QUERY))
203
  {
204
    root["error_message"]= exception.getErrorMessage();
205
    root["error_code"]= exception.getErrorCode();
206
  }
207
208
  while (result_set.next())
209
  {
210
    Json::Value json_row;
211
    for (size_t x= 0; x < result_set.getMetaData().getColumnCount(); x++)
212
    {
213
      if (not result_set.isNull(x))
214
      {
215
        json_row[x]= result_set.getString(x);
216
      }
217
    }
218
    root["result_set"].append(json_row);
219
  }
220
221
  root["query"]= input;
222
223
  Json::StyledWriter writer;
224
  std::string output= writer.write(root);
225
226
  evbuffer_add(buf, output.c_str(), output.length());
227
  evhttp_send_reply(req, HTTP_OK, "OK", buf);
228
}
229
2363.1.1 by Brian Aker
Fix memory leak in temporal and json server.
230
static void shutdown_event(int fd, short, void *arg)
231
{
232
  struct event_base *base= (struct event_base *)arg;
233
  event_base_loopbreak(base);
234
  close(fd);
235
}
236
237
238
static void run(struct event_base *base)
2283.4.1 by Stewart Smith
very basic HTTP server
239
{
2283.4.6 by Stewart Smith
add a simple SQL interface to json_server plugin. /0.1/sql takes a HTTP POST SQL command and returns the result set.
240
  internal::my_thread_init();
241
2283.4.1 by Stewart Smith
very basic HTTP server
242
  event_base_dispatch(base);
243
}
244
2363.1.1 by Brian Aker
Fix memory leak in temporal and json server.
245
246
class JsonServer : public drizzled::plugin::Daemon
247
{
248
private:
249
  drizzled::thread_ptr json_thread;
250
  in_port_t _port;
251
  struct evhttp *httpd;
252
  struct event_base *base;
253
  int wakeup_fd[2];
254
  struct event wakeup_event;
255
256
public:
257
  JsonServer(in_port_t port_arg) :
258
    drizzled::plugin::Daemon("JSON Server"),
259
    _port(port_arg),
260
    httpd(NULL),
261
    base(NULL)
262
  { }
263
264
  bool init()
265
  {
266
    if (pipe(wakeup_fd) < 0)
267
    {
268
      sql_perror("pipe");
269
      return false;
270
    }
271
272
    int returned_flags;
273
    if ((returned_flags= fcntl(wakeup_fd[0], F_GETFL, 0)) < 0)
274
    {
275
      sql_perror("fcntl:F_GETFL");
276
      return false;
277
    }
278
279
    if (fcntl(wakeup_fd[0], F_SETFL, returned_flags | O_NONBLOCK) < 0)
280
281
    {
282
      sql_perror("F_SETFL");
283
      return false;
284
    }
285
286
    if ((base= event_init()) == NULL)
287
    {
288
      sql_perror("event_init()");
289
      return false;
290
    }
291
292
    if ((httpd= evhttp_new(base)) == NULL)
293
    {
294
      sql_perror("evhttp_new()");
295
      return false;
296
    }
297
298
299
    if ((evhttp_bind_socket(httpd, "0.0.0.0", getPort())) == -1)
300
    {
301
      sql_perror("evhttp_bind_socket()");
302
      return false;
303
    }
304
305
    evhttp_set_cb(httpd, "/", process_root_request, NULL);
306
    evhttp_set_cb(httpd, "/0.1/version", process_api01_version_req, NULL);
307
    evhttp_set_cb(httpd, "/0.1/sql", process_api01_sql_req, NULL);
308
    evhttp_set_gencb(httpd, process_request, NULL);
309
310
    event_set(&wakeup_event, wakeup_fd[0], EV_READ | EV_PERSIST, shutdown_event, base);
311
    event_base_set(base, &wakeup_event);
312
    if (event_add(&wakeup_event, NULL) < 0)
313
    {
314
      sql_perror("event_add");
315
      return false;
316
    }
317
318
    json_thread.reset(new boost::thread((boost::bind(&run, base))));
319
320
    if (not json_thread)
321
      return false;
322
323
    return true;
324
  }
325
326
  ~JsonServer()
327
  {
328
    // If we can't write(), we will just muddle our way through the shutdown
329
    char buffer[1];
330
    buffer[0]= 4;
331
    if ((write(wakeup_fd[1], &buffer, 1)) == 1)
332
    {
333
      json_thread->join();
334
      evhttp_free(httpd);
335
      event_base_free(base);
336
    }
337
  }
338
};
339
2283.4.1 by Stewart Smith
very basic HTTP server
340
static int json_server_init(drizzled::module::Context &context)
341
{
342
  context.registerVariable(new sys_var_constrained_value_readonly<in_port_t>("port", port));
343
2363.1.1 by Brian Aker
Fix memory leak in temporal and json server.
344
  JsonServer *server;
345
  context.add(server= new JsonServer(port));
346
347
  if (server and not server->init())
348
  {
2283.4.1 by Stewart Smith
very basic HTTP server
349
    return -2;
2363.1.1 by Brian Aker
Fix memory leak in temporal and json server.
350
  }
351
352
  return bool(server) ? 0 : 1;
2283.4.1 by Stewart Smith
very basic HTTP server
353
}
354
355
static void init_options(drizzled::module::option_context &context)
356
{
357
  context("port",
2363.1.1 by Brian Aker
Fix memory leak in temporal and json server.
358
          po::value<port_constraint>(&port)->default_value(8086),
359
          _("Port number to use for connection or 0 for default (port 8086) "));
2283.4.1 by Stewart Smith
very basic HTTP server
360
}
361
362
} /* namespace json_server */
363
} /* namespace drizzle_plugin */
364
365
DRIZZLE_DECLARE_PLUGIN
366
{
367
  DRIZZLE_VERSION_ID,
368
  "json-server",
369
  "0.1",
370
  "Stewart Smith",
371
  "JSON HTTP interface",
372
  PLUGIN_LICENSE_GPL,
373
  drizzle_plugin::json_server::json_server_init,             /* Plugin Init */
374
  NULL, /* depends */
375
  drizzle_plugin::json_server::init_options    /* config options */
376
}
377
DRIZZLE_DECLARE_PLUGIN_END;