blob: a37931e9ea689ffed7cfd4ba1957b96d7e1e8611 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017, 2023, 2024 Marc Jakobi, github.com/MrcJkb
* fortiss GmbH
* Primetals Technologies Austria GmbH
* OFFIS e.V.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Marc Jakobi - initial implementation for HTTP clients
* Jose Cabral - Merge old HTTPIpLayer to this one and use CIEC_STRING
* Martin Melik Merkumians - change CIEC_STRING to std::string
* Malte Grave - fixed the handleHTTPResponse output
********************************************************************************/
#include "httplayer.h"
#include "httpparser.h"
#include "../../arch/devlog.h"
#include <string.h>
#include "basecommfb.h"
#include "http_handler.h"
#include "comtypes.h"
using namespace forte::com_infra;
using namespace std::string_literals;
CHttpComLayer::CHttpComLayer(CComLayer* paUpperLayer, CBaseCommFB* paComFB) :
CComLayer(paUpperLayer, paComFB), mInterruptResp(e_Nothing), mRequestType(e_NOTSET), mPort(80), mBufFillSize(0), mCorrectlyInitialized(false),
mHasParameterInSD(false) {
memset(mRecvBuffer, 0, sizeof(mRecvBuffer));
}
CHttpComLayer::~CHttpComLayer() {
closeConnection();
}
EComResponse CHttpComLayer::openConnection(char *paLayerParameter) {
EComResponse eRetVal = e_InitInvalidId;
if(checkSDsAndRDsType()) {
switch(mFb->getComServiceType()){
case e_Server:
if(1 == mFb->getNumSD()) {
mPath = std::string(paLayerParameter);
if(getExtEvHandler<CHTTP_Handler>().addServerPath(this, mPath)) {
eRetVal = e_InitOk;
}
} else {
DEVLOG_ERROR("[HTTP Layer] The FB with PARAM %s coudln't be initialized since only one SD is possible in HTTP Server which is for the response\n",
paLayerParameter);
}
break;
case e_Client:
eRetVal = openClientConnection(paLayerParameter);
break;
default:
// e_Publisher and e_Subscriber
eRetVal = e_ProcessDataInvalidObject;
break;
}
}
mCorrectlyInitialized = (eRetVal == e_InitOk);
return eRetVal;
}
EComResponse CHttpComLayer::openClientConnection(char* paLayerParameter) {
EComResponse eRetVal = e_InitInvalidId;
TPortId numberOfSD = mFb->getNumSD();
if(2 == mFb->getNumRD()) {
CParameterParser parser(paLayerParameter, ';', 3); //IP:PORT;POST|PUT|GET;[content-type]
if(handleContentAndRequestType(parser, parser.parseParameters()) && handleAddress(parser[0], numberOfSD)) {
switch(mRequestType){
case e_PUT:
case e_POST:
if(checkSDInPOSTAndPUT(numberOfSD)) {
CHttpParser::createPutPostRequest(mRequest, mHost, mPath, mReqData, mContentType, mRequestType);
eRetVal = e_InitOk;
DEVLOG_INFO("[HTTP Layer] FB with PUT/POST request initialized. Host: %s, Path: %s\n", mHost.c_str(), mPath.c_str());
}
break;
case e_GET:
if(0 != numberOfSD) {
DEVLOG_WARNING("[HTTP Layer] FB with GET request has SD which are ignore. If you want to have parameters, put them in the path in the PARAMS\n");
}
CHttpParser::createGetRequest(mRequest, mHost, mPath);
eRetVal = e_InitOk;
DEVLOG_INFO("[HTTP Layer] FB with GET request initialized. Host: %s, Path: %s\n", mHost.c_str(), mPath.c_str());
break;
default:
break;
}
}
} else {
DEVLOG_ERROR(
"[HTTP Layer] A client should ALWAYS have exactly 2 RD, the first one for the response code, and the second for the response body, even for PUT and POST\n");
}
mConnectionState = e_Disconnected;
return eRetVal;
}
bool CHttpComLayer::checkSDInPOSTAndPUT(size_t paNoOfSD) {
bool evetythingOK = true;
if(0 == paNoOfSD) {
if(mReqData.empty()) {
DEVLOG_ERROR("[HTTP Layer] You are using a POST/PUT FB but no data is defined as SD nor as parameters in PARAMS\n");
evetythingOK = false;
}
} else if(1 == paNoOfSD) {
if(!mReqData.empty()) {
DEVLOG_WARNING("[HTTP Layer] Parameters in PARAMS are ignored for PUT/POST request data and SDs are sent instead\n");
}
mHasParameterInSD = true;
} else {
DEVLOG_ERROR("[HTTP Layer] You can use maximum 1 SD\n");
evetythingOK = false;
}
return evetythingOK;
}
bool CHttpComLayer::checkSDsAndRDsType() const {
for(size_t i = 2; i < mFb->getNumSD(); i++) {
CIEC_ANY::EDataTypeID typeToCheck = mFb->getDI(static_cast<unsigned int>(i))->getDataTypeID();
if(CIEC_ANY::e_ANY != typeToCheck && CIEC_ANY::e_STRING != typeToCheck && CIEC_ANY::e_WSTRING != typeToCheck) {
DEVLOG_ERROR("[HTTP Layer] Client called %s has an invalid SD_%d\n", CStringDictionary::getInstance().get(mFb->getInstanceNameId()), i);
return false;
}
}
for(size_t i = 2; i < mFb->getNumRD(); i++) {
CIEC_ANY::EDataTypeID typeToCheck = mFb->getDO(static_cast<unsigned int>(i))->getDataTypeID();
if(CIEC_ANY::e_ANY != typeToCheck && CIEC_ANY::e_STRING != typeToCheck && CIEC_ANY::e_WSTRING != typeToCheck) {
DEVLOG_ERROR("[HTTP Layer] Client called %s has an invalid RD_%d\n", CStringDictionary::getInstance().get(mFb->getInstanceNameId()), i);
return false;
}
}
return true;
}
bool CHttpComLayer::storeRequestType(const char* paType) {
bool everythingOK = true;
if(0 == strcmp(paType, "POST")) {
mRequestType = e_POST;
} else if(0 == strcmp(paType, "PUT")) {
mRequestType = e_PUT;
} else if(0 == strcmp(paType, "GET")) {
mRequestType = e_GET;
} else {
DEVLOG_ERROR("[HTTP Layer] GET, PUT or POST must be defined, but %s was defined instead\n", paType);
everythingOK = false;
}
return everythingOK;
}
bool CHttpComLayer::handleContentAndRequestType(CParameterParser &paParser, size_t paNoOfParameters) {
bool everythingOK = true;
if(1 < paNoOfParameters) {
if(3 == paNoOfParameters) {
mContentType = std::string(paParser[2]);
} else {
mContentType = "text/html"s;
}
everythingOK = storeRequestType(paParser[1]);
} else {
everythingOK = false;
DEVLOG_ERROR("[HTTP Layer] Wrong parameters. It should be as IP:PORT;POST|PUT|GET;[content-type]\n");
}
return everythingOK;
}
bool CHttpComLayer::handleAddress(const char* paAddress, size_t paNoOfSDs) {
bool everythingOK = true;
//look for parameters
CParameterParser parser(paAddress, '?', 2); //IP:PORT/PATH?PARAMETERS
std::string addressToParse(paAddress);
if(2 == parser.parseParameters() && (e_PUT == mRequestType || e_POST == mRequestType)) {
if(0 == paNoOfSDs) { //if SDs are present, the parameters in PARAMS are ignored
mReqData = std::string(parser[1]);
mContentType = "application/x-www-form-urlencoded"s;
}
addressToParse = std::string(parser[0]);
}
char *firstSlash = strchr(addressToParse.data(), '/');
if(firstSlash) {
mPath = std::string(firstSlash);
*firstSlash = '\0';
CParameterParser portParser(addressToParse.c_str(), ':', 2);
if(2 == portParser.parseParameters()) {
mHost = std::string(portParser[0]);
mPort = static_cast<TForteUInt16>(forte::core::util::strtoul(portParser[1], nullptr, 10));
} else {
mHost = addressToParse;
DEVLOG_INFO("[HTTP Layer] No port was found on the parameter, using default 80\n");
}
} else {
everythingOK = false;
DEVLOG_ERROR("[HTTP Layer] No path was found on the parameter\n");
}
return everythingOK;
}
EComResponse CHttpComLayer::sendData(void *paData, unsigned int) {
mInterruptResp = e_Nothing;
if(mCorrectlyInitialized) {
switch(mFb->getComServiceType()){
case e_Server:
sendDataAsServer(paData);
break;
case e_Client:
sendDataAsClient(paData);
break;
default:
// e_Publisher and e_Subscriber
break;
}
} else {
DEVLOG_ERROR("[HTTP Layer]The FB is not initialized\n");
}
return mInterruptResp;
}
void CHttpComLayer::sendDataAsServer(const void *paData) {
bool error = false;
const CIEC_ANY* apoSDs = static_cast<const CIEC_ANY*>(paData);
if(!serializeData(apoSDs[0])) {
error = true;
} else {
CHttpParser::createResponse(mRequest, "HTTP/1.1 200 OK"s, "text/plain"s, mReqData);
}
if(error) {
getExtEvHandler<CHTTP_Handler>().forceClose(this);
mInterruptResp = e_ProcessDataDataTypeError;
} else {
getExtEvHandler<CHTTP_Handler>().sendServerAnswer(this, mRequest);
mInterruptResp = e_ProcessDataOk;
}
}
void CHttpComLayer::sendDataAsClient(const void *paData) {
bool error = false;
if(mHasParameterInSD) {
const CIEC_ANY* apoSDs = static_cast<const CIEC_ANY*>(paData);
if(!serializeData(apoSDs[0])) {
error = true;
DEVLOG_ERROR("[HTTP Layer] Error in data serialization\n");
} else {
if(!CHttpParser::changePutPostData(mRequest, mReqData)) {
DEVLOG_ERROR("[HTTP Layer] Wrong PUT/POST request when changing the data\n");
error = true;
}
}
}
if(!error) {
if(getExtEvHandler<CHTTP_Handler>().sendClientData(this, mRequest)) {
mInterruptResp = e_ProcessDataOk;
} else {
mInterruptResp = e_ProcessDataSendFailed;
DEVLOG_ERROR("[HTTP Layer] Sending request on TCP failed\n");
}
} else {
mInterruptResp = e_ProcessDataDataTypeError;
}
}
EComResponse CHttpComLayer::recvData(const void *paData, unsigned int paSize) {
mInterruptResp = e_Nothing;
if(mCorrectlyInitialized) {
memcpy(mRecvBuffer, paData, (paSize > cgIPLayerRecvBufferSize) ? cgIPLayerRecvBufferSize : paSize);
switch(mFb->getComServiceType()){
case e_Server:
DEVLOG_ERROR("[HTTP Layer] Receiving raw data as a Server? That's wrong, use the recvServerData function\n");
break;
case e_Client:
if(nullptr == paData) { //timeout occurred
mInterruptResp = e_ProcessDataRecvFaild;
} else {
if(e_ProcessDataOk != (mInterruptResp = handleHTTPResponse(mRecvBuffer))) {
DEVLOG_ERROR("[HTTP Layer] FB with host: %s:%u couldn't handle the HTTP response\n", mHost.c_str(), mPort);
} else {
//TODO Trigger event?
}
}
break;
default:
break;
}
} else {
DEVLOG_ERROR("[HTTP Layer] The FB is not initialized\n");
}
if(e_ProcessDataOk == mInterruptResp) {
mFb->interruptCommFB(this);
}
return mInterruptResp;
}
EComResponse forte::com_infra::CHttpComLayer::recvServerData(CSinglyLinkedList<std::string>&, CSinglyLinkedList<std::string>& paParameterValues) {
//for now, the parameterNames are not taken in account, and the parameters are put in the same order they arrived
mInterruptResp = e_Nothing;
bool failed = false;
if(0 < mFb->getNumSD()) {
unsigned int noOfParameters = 0;
for(CSinglyLinkedList<std::string>::Iterator iter = paParameterValues.begin(); iter != paParameterValues.end(); ++iter) {
noOfParameters++;
}
if(noOfParameters == mFb->getNumRD()) {
noOfParameters = 0;
for(CSinglyLinkedList<std::string>::Iterator iter = paParameterValues.begin(); iter != paParameterValues.end(); ++iter) {
mFb->getRDs()[noOfParameters++]->setValue(CIEC_STRING(*iter));
}
} else {
DEVLOG_ERROR("[HTTP Layer] FB with path %s received a number of parameters of %u, while it has %u SDs\n", mPath.c_str(),
static_cast<TForteUInt16>(noOfParameters), mFb->getNumRD());
failed = true;
}
}
if(failed) {
std::string toSend;
std::string result("HTTP/1.1 400 Bad Request"s);
mReqData.clear();
CHttpParser::createResponse(toSend, result, mContentType, mReqData);
getExtEvHandler<CHTTP_Handler>().sendServerAnswerFromRecv(this, toSend);
mInterruptResp = e_ProcessDataDataTypeError;
} else {
mInterruptResp = e_ProcessDataOk;
}
if(e_ProcessDataOk == mInterruptResp) {
mFb->interruptCommFB(this);
}
return mInterruptResp;
}
EComResponse CHttpComLayer::handleHTTPResponse(char *paData) {
DEVLOG_DEBUG("[HTTP Layer] Handling received HTTP response\n");
EComResponse eRetVal = e_ProcessDataRecvFaild;
if(mFb != nullptr) {
CIEC_ANY** apoRDs = mFb->getRDs();
// Interpret HTTP response and set output status according to success/failure.
std::string responseCode;
std::string output;
CHttpParser::parseResponse(output, responseCode, paData) ? eRetVal = e_ProcessDataOk : eRetVal = e_ProcessDataRecvFaild;
apoRDs[0]->setValue(CIEC_STRING(responseCode));
apoRDs[1]->setValue(CIEC_STRING(output));
} else {
DEVLOG_ERROR("[HTTP Layer] No FB defined\n");
}
return eRetVal;
}
EComResponse CHttpComLayer::processInterrupt() {
mInterruptResp = e_ProcessDataOk;
return mInterruptResp;
}
void CHttpComLayer::closeConnection() {
if(e_Server == mFb->getComServiceType()) {
getExtEvHandler<CHTTP_Handler>().removeServerPath(mPath);
}
getExtEvHandler<CHTTP_Handler>().forceClose(this);
}
bool CHttpComLayer::serializeData(const CIEC_ANY& paCIECData) {
size_t bufferSize = paCIECData.getToStringBufferSize();
mReqData.reserve(static_cast<TForteUInt16>(bufferSize));
char *buffer = mReqData.data();
int nWrittenBytes;
switch(paCIECData.getDataTypeID()){
case CIEC_ANY::e_WSTRING:
nWrittenBytes = static_cast<const CIEC_WSTRING&>(paCIECData).toUTF8(buffer, bufferSize, false);
break;
case CIEC_ANY::e_STRING:
nWrittenBytes = static_cast<const CIEC_STRING&>(paCIECData).toUTF8(buffer, bufferSize, false);
break;
default:
nWrittenBytes = paCIECData.toString(buffer, bufferSize);
}
nWrittenBytes = nWrittenBytes > -1 ? nWrittenBytes : 0;
mReqData.assign(buffer, static_cast<TForteUInt16>(nWrittenBytes));
return true;
}
std::string& forte::com_infra::CHttpComLayer::getHost() {
return mHost;
}
TForteUInt16 forte::com_infra::CHttpComLayer::getPort() const {
return mPort;
}