diff options
author | Stefan Radomski <github@mintwerk.de> | 2017-06-13 10:19:24 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-13 10:19:24 (GMT) |
commit | b3a2d91805feb81f79ee52c30a077521912b0bf9 (patch) | |
tree | 8b57e9244576eaa1c721df44899009f3b8d10f05 /src/uscxml/plugins/ioprocessor/http | |
parent | 4b861a6af4eec8a58d3515e871ccdadd44a182fd (diff) | |
parent | a43c42980727e0376c6bfa44576a54e6d3c26687 (diff) | |
download | uscxml-b3a2d91805feb81f79ee52c30a077521912b0bf9.zip uscxml-b3a2d91805feb81f79ee52c30a077521912b0bf9.tar.gz uscxml-b3a2d91805feb81f79ee52c30a077521912b0bf9.tar.bz2 |
Merge pull request #146 from tklab-tud/sradomski
respond element and proper http ioproc
Diffstat (limited to 'src/uscxml/plugins/ioprocessor/http')
-rw-r--r-- | src/uscxml/plugins/ioprocessor/http/HTTPIOProcessor.cpp | 329 | ||||
-rw-r--r-- | src/uscxml/plugins/ioprocessor/http/HTTPIOProcessor.h | 116 |
2 files changed, 445 insertions, 0 deletions
diff --git a/src/uscxml/plugins/ioprocessor/http/HTTPIOProcessor.cpp b/src/uscxml/plugins/ioprocessor/http/HTTPIOProcessor.cpp new file mode 100644 index 0000000..276fd4e --- /dev/null +++ b/src/uscxml/plugins/ioprocessor/http/HTTPIOProcessor.cpp @@ -0,0 +1,329 @@ +/** + * @file + * @author 2017 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program 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. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#include "uscxml/Common.h" + +#include "uscxml/plugins/ioprocessor/http/HTTPIOProcessor.h" +#include "uscxml/messages/Event.h" +#include "uscxml/util/DOM.h" + +#include <event2/dns.h> +#include <event2/buffer.h> +#include <event2/keyvalq_struct.h> + +#include <string.h> + +#include "uscxml/interpreter/Logging.h" +#include <boost/algorithm/string.hpp> + +#ifdef _WIN32 +#define NOMINMAX +#include <winsock2.h> +#include <windows.h> +#endif + +#ifndef _WIN32 +#include <netdb.h> +#include <arpa/inet.h> +#endif + +#ifdef BUILD_AS_PLUGINS +#include <Pluma/Connector.hpp> +#endif + +namespace uscxml { + +#ifndef ioprocessor_scxml_EXPORTS +# ifdef BUILD_AS_PLUGINS +PLUMA_CONNECTOR +bool pluginConnect(pluma::Host& host) { + host.add( new HTTPIOProcessorProvider() ); + return true; +} +# endif +#endif + +HTTPIOProcessor::HTTPIOProcessor() { + HTTPServer::getInstance(); +} + +HTTPIOProcessor::~HTTPIOProcessor() { + HTTPServer* httpServer = HTTPServer::getInstance(); + httpServer->unregisterServlet(this); +} + + +std::shared_ptr<IOProcessorImpl> HTTPIOProcessor::create(IOProcessorCallbacks* callbacks) { + std::shared_ptr<HTTPIOProcessor> io(new HTTPIOProcessor()); + io->_callbacks = callbacks; + + // register at http server + std::string path = callbacks->getName(); + int i = 2; + while (!HTTPServer::registerServlet(path, io.get())) { + std::stringstream ss; + ss << callbacks->getName() << i++; + path = ss.str(); + } + + return io; +} + +Data HTTPIOProcessor::getDataModelVariables() { + Data data; + + // we are not connected! + if(_url.length() == 0) + return data; + + data.compound["location"] = Data(_url, Data::VERBATIM); + data.compound["timeout"] = Data(_timeoutS, Data::INTERPRETED); + + URL url(_url); + data.compound["host"] = Data(url.host(), Data::VERBATIM); + data.compound["port"] = Data(url.port(), Data::VERBATIM); + data.compound["path"] = Data(url.path(), Data::VERBATIM); + data.compound["scheme"] = Data(url.scheme(), Data::VERBATIM); + + std::list<std::string> pathComps = url.pathComponents(); + std::list<std::string>::const_iterator pathCompIter = pathComps.begin(); + while(pathCompIter != pathComps.end()) { + data.compound["pathComponents"].array.push_back(Data(*pathCompIter, Data::VERBATIM)); + pathCompIter++; + } + + return data; +} + +bool HTTPIOProcessor::requestFromHTTP(const HTTPServer::Request& req) { + Event event = req; + event.eventType = Event::EXTERNAL; + + time_t now = std::time(0); + _unansweredRequests[req.getUUID()] = std::make_pair(now, req); + + // remove stale requests + for (auto reqIter = _unansweredRequests.begin(); reqIter != _unansweredRequests.end();) { + if (now > reqIter->second.first + _timeoutS) { + evhttp_send_reply(reqIter->second.second.evhttpReq, 504, "Event was not responded to in time", NULL); + _unansweredRequests.erase(reqIter++); + } else { + ++reqIter; + } + } + + /** + * If a single instance of the parameter '_scxmleventname' is present, the + * SCXML Processor must use its value as the name of the SCXML event that it + * raises. + */ + + { + // if we sent ourself an event it will end up here + // this will call the const subscript operator + if (req.data.at("content").hasKey("_scxmleventname")) { + event.name = req.data.at("content").at("_scxmleventname").atom; + } + if (req.data.at("content").hasKey("content")) { + event.data.atom = req.data.at("content").at("content").atom; + } + } + + // if we used wget, it will end up here - unify? + if (req.data.hasKey("content")) { + const Data& data = req.data["content"]; + for(std::map<std::string, Data>::const_iterator compIter = data.compound.begin(); + compIter!= data.compound.end(); compIter++) { + if (compIter->first == "content") { + event.data.atom = compIter->second.atom; + } else { + event.data[compIter->first] = compIter->second; + } + } + } + + if (req.data.hasKey("header")) { + const Data& data = req.data["header"]; + for(std::map<std::string, Data>::const_iterator compIter = data.compound.begin(); + compIter!= data.compound.end(); compIter++) { + if (compIter->first == "_scxmleventname") { + event.name = compIter->second.atom; + } + } + } + + // test 532 + if (event.name.length() == 0) + event.name = "http." + req.data.compound.at("type").atom; + + eventToSCXML(event, USCXML_IOPROC_HTTP_TYPE, req.getUUID()); + + // do not reply + // evhttp_send_reply(req.evhttpReq, 200, "OK", NULL); + return true; +} + +bool HTTPIOProcessor::isValidTarget(const std::string& target) { + try { + URL url(target); + if (url.scheme().compare("http") != 0) + return false; + + return true; + } catch (ErrorEvent e) { + } + return false; +} + +void HTTPIOProcessor::eventFromSCXML(const std::string& target, const Event& event) { + + // TODO: is this still needed with isValidTarget()? + if (target.length() == 0) { + _callbacks->enqueueInternal(Event("error.communication", Event::PLATFORM)); + return; + } + + bool isLocal = target == _url; + URL targetURL(target); + std::stringstream kvps; + std::string kvpSeperator; + + // event name + if (event.name.size() > 0) { + char* eventNameCStr = evhttp_encode_uri("_scxmleventname"); + char* eventValueCStr = evhttp_encode_uri(event.name.c_str()); + kvps << kvpSeperator << eventNameCStr << "=" << eventValueCStr; + kvpSeperator = "&"; + targetURL.addOutHeader("_scxmleventname", eventValueCStr); + free(eventNameCStr); + free(eventValueCStr); + } + + // event namelist + if (event.namelist.size() > 0) { + std::map<std::string, Data>::const_iterator namelistIter = event.namelist.begin(); + while (namelistIter != event.namelist.end()) { + char* keyCStr = evhttp_encode_uri(namelistIter->first.c_str()); + // this is simplified - Data might be more elaborate than a simple string atom + char* valueCStr = evhttp_encode_uri(namelistIter->second.atom.c_str()); + kvps << kvpSeperator << keyCStr << "=" << valueCStr; + free(keyCStr); + free(valueCStr); + kvpSeperator = "&"; + targetURL.addOutHeader(namelistIter->first, namelistIter->second); + namelistIter++; + } + } + + // event params + if (event.params.size() > 0) { + std::multimap<std::string, Data>::const_iterator paramIter = event.params.begin(); + while (paramIter != event.params.end()) { + char* keyCStr = evhttp_encode_uri(paramIter->first.c_str()); + // this is simplified - Data might be more elaborate than a simple string atom + char* valueCStr = evhttp_encode_uri(paramIter->second.atom.c_str()); + kvps << kvpSeperator << keyCStr << "=" << valueCStr; + free(keyCStr); + free(valueCStr); + kvpSeperator = "&"; + targetURL.addOutHeader(paramIter->first, paramIter->second); + paramIter++; + } + } + + // try hard to find actual content + char* keyCStr = evhttp_encode_uri("content"); + if (!event.data.empty()) { + char* valueCStr = NULL; + if (event.data.atom.length() || event.data.array.size() || event.data.compound.size()) { + valueCStr = evhttp_encode_uri(Data::toJSON(event.data).c_str()); + } else if(event.data.node) { + std::stringstream xmlStream; + xmlStream << event.data.node; + valueCStr = evhttp_encode_uri(xmlStream.str().c_str()); + } else if(event.data.binary) { + valueCStr = evhttp_encode_uri(event.data.binary.base64().c_str()); + } + if (valueCStr != NULL) { + kvps << kvpSeperator << keyCStr << "=" << valueCStr; + free(valueCStr); + kvpSeperator = "&"; + } + } + free(keyCStr); + + targetURL.setOutContent(kvps.str()); + targetURL.addOutHeader("Content-Type", "application/x-www-form-urlencoded"); + + targetURL.setRequestType(URLRequestType::POST); + targetURL.addMonitor(this); + + _sendRequests[event.sendid] = std::make_pair(targetURL, event); + if (isLocal) { + // test201 use a blocking request with local communication + targetURL.download(true); + } else { + URLFetcher::fetchURL(targetURL); + } +} + +void HTTPIOProcessor::downloadStarted(const URL& url) {} + +void HTTPIOProcessor::downloadCompleted(const URL& url) { + std::map<std::string, std::pair<URL, Event> >::iterator reqIter = _sendRequests.begin(); + while(reqIter != _sendRequests.end()) { + if (reqIter->second.first == url) { + // test513 + std::string statusCode = url.getStatusCode(); + if (statusCode.length() > 0) { + std::string statusPrefix = statusCode.substr(0,1); + std::string statusRest = statusCode.substr(1); + Event event; + event.data = url; + event.name = "HTTP." + statusPrefix + "." + statusRest; + eventToSCXML(event, USCXML_IOPROC_HTTP_TYPE, std::string(_url)); + } + _sendRequests.erase(reqIter); + return; + } + reqIter++; + } + assert(false); +} + +void HTTPIOProcessor::downloadFailed(const URL& url, int errorCode) { + + std::map<std::string, std::pair<URL, Event> >::iterator reqIter = _sendRequests.begin(); + while(reqIter != _sendRequests.end()) { + if (reqIter->second.first == url) { + Event failEvent; + failEvent.name = "error.communication"; + eventToSCXML(failEvent, USCXML_IOPROC_HTTP_TYPE, std::string(_url)); + + _sendRequests.erase(reqIter); + return; + } + reqIter++; + } + assert(false); + +} + + +} diff --git a/src/uscxml/plugins/ioprocessor/http/HTTPIOProcessor.h b/src/uscxml/plugins/ioprocessor/http/HTTPIOProcessor.h new file mode 100644 index 0000000..aefc771 --- /dev/null +++ b/src/uscxml/plugins/ioprocessor/http/HTTPIOProcessor.h @@ -0,0 +1,116 @@ +/** + * @file + * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program 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. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see <http://www.opensource.org/licenses/bsd-license>. + * @endcond + */ + +#ifndef HTTPIOPROCESSOR_H_645835 +#define HTTPIOPROCESSOR_H_645835 + +#include "uscxml/config.h" + +extern "C" { +#include <event2/http.h> +#include <event2/http_struct.h> +} + +#include <chrono> +#include <ctime> + +// why is it duplicated from Common.h here? + +#if defined(_WIN32) && !defined(USCXML_STATIC) +# if (defined ioprocessor_http_EXPORTS || defined USCXML_EXPORT) +# define USCXML_PLUGIN_API __declspec(dllexport) +# else +# define USCXML_PLUGIN_API __declspec(dllimport) +# endif +#else +# define USCXML_PLUGIN_API +#endif + +#include "uscxml/server/HTTPServer.h" +#include "uscxml/interpreter/InterpreterImpl.h" +#include "uscxml/plugins/IOProcessorImpl.h" + +#ifndef _WIN32 +#include <sys/time.h> +#endif + +#ifdef BUILD_AS_PLUGINS +#include "uscxml/plugins/Plugins.h" +#endif + +#define USCXML_IOPROC_HTTP_TYPE "http://www.w3.org/TR/scxml/#HTTPEventProcessor" + +namespace uscxml { + +/** + * @ingroup ioproc + * The http I/O processor. + */ +class USCXML_PLUGIN_API HTTPIOProcessor : public IOProcessorImpl, public HTTPServlet, public URLMonitor { +public: + HTTPIOProcessor(); + virtual ~HTTPIOProcessor(); + virtual std::shared_ptr<IOProcessorImpl> create(uscxml::IOProcessorCallbacks* callbacks); + + virtual std::list<std::string> getNames() { + std::list<std::string> names; + names.push_back("http"); + names.push_back(USCXML_IOPROC_HTTP_TYPE); + return names; + } + + virtual void eventFromSCXML(const std::string& target, const Event& event); + virtual bool isValidTarget(const std::string& target); + + Data getDataModelVariables(); + + /// HTTPServlet + bool requestFromHTTP(const HTTPServer::Request& req); + void setURL(const std::string& url) { + _url = url; + } + + bool canAdaptPath() { + return true; + } + + // URLMonitor + void downloadStarted(const URL& url); + void downloadCompleted(const URL& url); + void downloadFailed(const URL& url, int errorCode); + + std::map<std::string, std::pair<std::time_t, HTTPServer::Request> >& getUnansweredRequests() { + return _unansweredRequests; + } + +protected: + std::string _url; + size_t _timeoutS = WITH_IOPROC_HTTP_TIMEOUT; + std::map<std::string, std::pair<URL, Event> > _sendRequests; + std::map<std::string, std::pair<std::time_t, HTTPServer::Request> > _unansweredRequests; + +}; + +#ifdef BUILD_AS_PLUGINS +PLUMA_INHERIT_PROVIDER(HTTPIOProcessor, IOProcessorImpl) +#endif + +} + +#endif /* end of include guard: HTTPIOPROCESSOR_H_645835 */ |