-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Access handlers using a HTTP server. The HTTP Server is using the good old userlevel sockets, it is not handled inside Click's data path. This is exactly like ControlSocket, except it works in HTTP. GET /element/handler will issue a read on element's handler PUT /element/handler will issue a write passing the body as the handler string. Parameters like ?id=7 append to the request will be lost POST /element/handler?id=7 will issue a write passing the "id=7\n<body>" as data where <body> is the request data DELETE and other requests will translate as read or a write according to the rfc but with the lowercase request as prefix. Eg : DELETE /element/handler?id=7 will do a write on the "delete_handler" passing data id=7, body will be lost Actually other than DELETE are lost but I'll welcome pull requests GET / or element class like /myclass/ will list the sub-elements GET /element/ will list the handlers This is very convenient with the JSON elements to make Click RESTful. We have some internal elements that respond to their handlers as JSON, allowing to use Click with an API. It's working but corner cases are unsure. Good enough for research... Userlevel only. Requires libmicrohttpd (available in all good OS packagers)
- Loading branch information
Tom Barbette
committed
Oct 10, 2017
1 parent
a1d5ae4
commit 7dc2031
Showing
3 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
#include <click/config.h> | ||
|
||
|
||
#include <microhttpd.h> | ||
#include "httpserver.hh" | ||
|
||
#include <click/args.hh> | ||
#include <click/standard/scheduleinfo.hh> | ||
#include <click/error.hh> | ||
#include <click/router.hh> | ||
#include <sys/select.h> | ||
|
||
CLICK_DECLS | ||
|
||
|
||
static int ahc_policy(void *cls, const struct sockaddr *addr, socklen_t addrlen) { | ||
(void)cls; | ||
(void)addr; | ||
(void)addrlen; | ||
return MHD_YES; | ||
} | ||
|
||
HTTPServer::HTTPServer() : _verbose(false), _port(80), _daemon(0) { | ||
} | ||
|
||
HTTPServer::~HTTPServer() { | ||
|
||
} | ||
|
||
|
||
void HTTPServer::selected(int fd, int mask) { | ||
remove_select(fd, mask); | ||
MHD_run(_daemon); | ||
update_fd_set(); | ||
} | ||
|
||
void HTTPServer::update_fd_set() { | ||
int max_fd = 0; | ||
fd_set _read_fd_set,_write_fd_set,_except_fd_set; | ||
|
||
FD_ZERO(&_read_fd_set); | ||
FD_ZERO(&_write_fd_set); | ||
FD_ZERO(&_except_fd_set); | ||
|
||
if (MHD_get_fdset(_daemon, &_read_fd_set, &_write_fd_set, &_except_fd_set, &max_fd) != MHD_YES) { | ||
click_chatter("Could not get fd set"); | ||
return; | ||
} | ||
for (int i = 0; i <= max_fd; i++) { | ||
if (FD_ISSET(i, &_read_fd_set)) { | ||
add_select(i,SELECT_READ); | ||
} | ||
if (FD_ISSET(i, &_write_fd_set)) { | ||
add_select(i,SELECT_WRITE); | ||
} | ||
} | ||
} | ||
|
||
int HTTPServer::configure(Vector<String> &conf, ErrorHandler *errh) { | ||
if (Args(conf, this, errh) | ||
.read_p("PORT", _port) //TODO AUTH | ||
.read("VERBOSE", _verbose) | ||
.complete() < 0) | ||
return -1; | ||
|
||
return 0; | ||
} | ||
|
||
|
||
int HTTPServer::initialize(ErrorHandler *errh) { | ||
_daemon = MHD_start_daemon(MHD_USE_DEBUG, | ||
_port, | ||
&ahc_policy, | ||
NULL, | ||
&ahc_echo, | ||
(void*)this, | ||
MHD_OPTION_END); | ||
if (_daemon == NULL) | ||
return 1; | ||
|
||
update_fd_set(); | ||
|
||
return 0; | ||
} | ||
|
||
|
||
int HTTPServer::ahc_echo( | ||
void *cls, | ||
struct MHD_Connection *connection, | ||
const char *url, | ||
const char *method, | ||
const char *version, | ||
const char *upload_data, | ||
size_t *upload_data_size, | ||
void **con_cls) { | ||
HTTPServer *server = reinterpret_cast<HTTPServer *>(cls); | ||
|
||
int ret = MHD_YES; | ||
|
||
if (NULL == *con_cls) | ||
{*con_cls = new String(""); | ||
|
||
return MHD_YES; | ||
} | ||
|
||
if (server->_verbose) | ||
click_chatter("[%s] %s",method,url); | ||
|
||
|
||
//Processing request | ||
String body; | ||
int status; | ||
struct MHD_Response *response; | ||
const Handler *h; | ||
|
||
String path = String(&url[1]); | ||
if (path[0] == '/') | ||
path = path.substring(1); | ||
|
||
Element *e = 0; | ||
String ename; | ||
String fullpath = ""; | ||
do { | ||
int pos = path.find_left('/'); | ||
String en; | ||
if (pos == -1) { | ||
en = path; | ||
} else { | ||
en = path.substring(0,pos); | ||
} | ||
|
||
Element* et = server->router()->find(fullpath + en); | ||
if (!et) { | ||
break; | ||
} | ||
e = et; | ||
ename = en; | ||
fullpath = en + "/"; | ||
if (pos == -1) { | ||
path = ""; | ||
break; | ||
} else { | ||
path = path.substring(pos + 1); | ||
} | ||
} while (1); | ||
|
||
|
||
String hname; | ||
String param; | ||
int isNotPost = strcmp (method, "POST"); | ||
int isNotPut = strcmp (method, "PUT"); | ||
int pos = path.find_left('/'); | ||
if (pos == -1) { | ||
hname = path; | ||
param = ""; | ||
} else { | ||
hname = path.substring(0,pos); | ||
param = path.substring(pos + 1); | ||
} | ||
click_chatter("Element '%s', handler '%s', param '%s'",ename.c_str(), hname.c_str(),param.c_str()); | ||
|
||
if (!e) { | ||
if (hname == "" || ename == "") { | ||
e = server->router()->root_element(); | ||
} else { | ||
body = "No element named '" + ename + "'"; | ||
status = 404; | ||
goto send; | ||
} | ||
} | ||
|
||
|
||
if ((hname == "") && strcmp("GET",method) == 0) { | ||
/*Json jelements = Json::make_array(); | ||
for (int i = 0; i < server->router()->nelements(); i++) { | ||
String ename = server->router()->element(i)->name(); | ||
Json je = Json::make_object(); | ||
je.set("name",ename); | ||
jelements.push_back(je); | ||
} | ||
body = jelements.unparse(); | ||
status = MHD_HTTP_OK; | ||
goto send;*/ | ||
if (ename.length() > 0) { | ||
hname = "handlers"; | ||
} else { | ||
hname = "list"; | ||
} | ||
} | ||
|
||
// Then find handler. | ||
if (strcmp("GET",method) == 0) { | ||
h = Router::handler(e, hname); | ||
if (h && h->visible()) { | ||
if (h->readable()) { | ||
if (h->flags() & Handler::f_read_param) { | ||
body = h->call_read(e, param, ErrorHandler::default_handler()); | ||
} else { | ||
body = h->call_read(e, ErrorHandler::default_handler()); | ||
} | ||
status = MHD_HTTP_OK; | ||
} else { | ||
body = "This request is not readable"; | ||
status = MHD_HTTP_BAD_REQUEST; | ||
} | ||
goto send; | ||
} else { | ||
goto bad_handler; | ||
} | ||
} else if (0 == isNotPost or 0 == isNotPut) { | ||
if (isNotPost) { | ||
hname = "put_" + hname; | ||
} | ||
h = Router::handler(e, hname); | ||
if (h && h->visible()) { | ||
if (*upload_data_size != 0) { | ||
static_cast<String*>(*con_cls)->append(String(upload_data, *upload_data_size)); | ||
*upload_data_size = 0; | ||
|
||
return MHD_YES; | ||
} else { | ||
String data = *static_cast<String*>(*con_cls); | ||
click_chatter("Last call with data %s",data.c_str()); | ||
if (h->writable()) { | ||
int ret; | ||
if (isNotPost) | ||
ret = h->call_write(param + "\n" + data, e, ErrorHandler::default_handler()); | ||
else | ||
ret = h->call_write(data, e, ErrorHandler::default_handler()); | ||
if (ret == 0) { | ||
body = "success"; | ||
} else { | ||
body = "error"; | ||
} | ||
status = MHD_HTTP_OK; | ||
} else { | ||
body = "This request is not writable"; | ||
status = MHD_HTTP_BAD_REQUEST; | ||
} | ||
delete static_cast<String*>(*con_cls); | ||
} | ||
goto send; | ||
} else { | ||
goto bad_handler; | ||
} | ||
} else if (strcmp("DELETE",method) == 0) { | ||
hname = "delete_" + hname; | ||
h = Router::handler(e, hname); | ||
if (h && h->visible()) { | ||
int ret = h->call_write(param, e, ErrorHandler::default_handler()); | ||
if (ret == 0) { | ||
body = "success"; | ||
} else { | ||
body = "error"; | ||
} | ||
status = MHD_HTTP_OK; | ||
goto send; | ||
} else { | ||
goto bad_handler; | ||
} | ||
} else { | ||
body = "Unsupported method"; | ||
status = MHD_HTTP_METHOD_NOT_ALLOWED; | ||
goto send; | ||
} | ||
assert(false); | ||
|
||
bad_handler: | ||
body = "Invalid path '" + String(url) + "' or no " + hname + " in " + ename; | ||
status = 404; | ||
goto send; | ||
|
||
send: | ||
response = MHD_create_response_from_buffer (body.length(), | ||
(void*)body.c_str(), | ||
MHD_RESPMEM_MUST_COPY); | ||
if (NULL == response) { | ||
click_chatter("Could not create response"); | ||
return MHD_NO; | ||
} | ||
ret = MHD_queue_response(connection, | ||
status, | ||
response); | ||
MHD_destroy_response(response); | ||
|
||
return ret; | ||
} | ||
|
||
void HTTPServer::cleanup(CleanupStage) { | ||
if (_daemon != NULL) | ||
MHD_stop_daemon(_daemon); | ||
} | ||
|
||
CLICK_ENDDECLS | ||
ELEMENT_REQUIRES(userlevel microhttpd) | ||
EXPORT_ELEMENT(HTTPServer) | ||
ELEMENT_MT_SAFE(HTTPServer) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// -*- c-basic-offset: 4 -*- | ||
#ifndef CLICK_HTTPSERVER_HH | ||
#define CLICK_HTTPSERVER_HH | ||
#include <click/element.hh> | ||
#include <click/etheraddress.hh> | ||
#include <click/task.hh> | ||
#include <click/notifier.hh> | ||
#include <click/multithread.hh> | ||
#include <click/ring.hh> | ||
CLICK_DECLS | ||
|
||
/* | ||
=c | ||
HTTPServer */ | ||
|
||
class HTTPServer : public Element { public: | ||
|
||
HTTPServer() CLICK_COLD; | ||
~HTTPServer() CLICK_COLD; | ||
|
||
const char *class_name() const { return "HTTPServer"; } | ||
const char *port_count() const { return PORTS_0_0; } | ||
|
||
|
||
int configure(Vector<String> &, ErrorHandler *) CLICK_COLD; | ||
int initialize(ErrorHandler *) CLICK_COLD; | ||
void cleanup(CleanupStage) CLICK_COLD; | ||
|
||
void selected(int fd, int mask); | ||
void update_fd_set(); | ||
|
||
static int ahc_echo( | ||
void *cls, | ||
struct MHD_Connection *connection, | ||
const char *url, | ||
const char *method, | ||
const char *version, | ||
const char *upload_data, | ||
size_t *upload_data_size, | ||
void **ptr | ||
); | ||
private: | ||
int _port; | ||
bool _verbose; | ||
struct MHD_Daemon * _daemon; | ||
|
||
|
||
class Request { | ||
public: | ||
struct MHD_Connection * connection; | ||
|
||
Request() : connection(0) { | ||
|
||
} | ||
|
||
}; | ||
MPMCRing<Request*,32> _requests; | ||
|
||
}; | ||
|
||
CLICK_ENDDECLS | ||
#endif |