[DEV] rework ENET
This commit is contained in:
parent
f0c425cd29
commit
97acf584f8
400
enet/Http.cpp
400
enet/Http.cpp
@ -9,37 +9,146 @@
|
||||
#include <map>
|
||||
#include <etk/stdTools.h>
|
||||
#include <string.h>
|
||||
#if 0
|
||||
#include <unistd.h>
|
||||
|
||||
static std::map<enet::HTTPAnswerCode, std::string> protocolName = {
|
||||
{enet::HTTPAnswerCode::c100_continue, "Continue"},
|
||||
{enet::HTTPAnswerCode::c101_switchingProtocols, "Switching Protocols"},
|
||||
{enet::HTTPAnswerCode::c103_checkpoint, "Checkpoint"},
|
||||
{enet::HTTPAnswerCode::c200_ok, "OK"},
|
||||
{enet::HTTPAnswerCode::c201_created, "Created"},
|
||||
{enet::HTTPAnswerCode::c202_accepted, "Accepted"},
|
||||
{enet::HTTPAnswerCode::c203_nonAuthoritativeInformation, "Non-Authoritative Information"},
|
||||
{enet::HTTPAnswerCode::c204_noContent, "No Content"},
|
||||
{enet::HTTPAnswerCode::c205_resetContent, "Reset Content"},
|
||||
{enet::HTTPAnswerCode::c206_partialContent, "Partial Content"},
|
||||
{enet::HTTPAnswerCode::c300_multipleChoices, "Multiple Choices"},
|
||||
{enet::HTTPAnswerCode::c301_movedPermanently, "Moved Permanently"},
|
||||
{enet::HTTPAnswerCode::c302_found, "Found"},
|
||||
{enet::HTTPAnswerCode::c303_seeOther, "See Other"},
|
||||
{enet::HTTPAnswerCode::c304_notModified, "Not Modified"},
|
||||
{enet::HTTPAnswerCode::c306_switchProxy, "Switch Proxy"},
|
||||
{enet::HTTPAnswerCode::c307_temporaryRedirect, "Temporary Redirect"},
|
||||
{enet::HTTPAnswerCode::c308_resumeIncomplete, "Resume Incomplete"},
|
||||
{enet::HTTPAnswerCode::c400_badRequest, "Bad Request"},
|
||||
{enet::HTTPAnswerCode::c401_unauthorized, "Unauthorized"},
|
||||
{enet::HTTPAnswerCode::c402_paymentRequired, "Payment Required"},
|
||||
{enet::HTTPAnswerCode::c403_forbidden, "Forbidden"},
|
||||
{enet::HTTPAnswerCode::c404_notFound, "Not Found"},
|
||||
{enet::HTTPAnswerCode::c405_methodNotAllowed, "Method Not Allowed"},
|
||||
{enet::HTTPAnswerCode::c406_notAcceptable, "Not Acceptable"},
|
||||
{enet::HTTPAnswerCode::c407_proxyAuthenticationRequired, "Proxy Authentication Required"},
|
||||
{enet::HTTPAnswerCode::c408_requestTimeout, "Request Timeout"},
|
||||
{enet::HTTPAnswerCode::c409_conflict, "Conflict"},
|
||||
{enet::HTTPAnswerCode::c410_gone, "Gone"},
|
||||
{enet::HTTPAnswerCode::c411_lengthRequired, "Length Required"},
|
||||
{enet::HTTPAnswerCode::c412_preconditionFailed, "Precondition Failed"},
|
||||
{enet::HTTPAnswerCode::c413_requestEntityTooLarge, "Request Entity Too Large"},
|
||||
{enet::HTTPAnswerCode::c414_requestURITooLong, "Request-URI Too Long"},
|
||||
{enet::HTTPAnswerCode::c415_unsupportedMediaType, "Unsupported Media Type"},
|
||||
{enet::HTTPAnswerCode::c416_requestedRangeNotSatisfiable, "Requested Range Not Satisfiable"},
|
||||
{enet::HTTPAnswerCode::c417_expectationFailed, "Expectation Failed"},
|
||||
{enet::HTTPAnswerCode::c500_internalServerError, "Internal Server Error"},
|
||||
{enet::HTTPAnswerCode::c501_notImplemented, "Not Implemented"},
|
||||
{enet::HTTPAnswerCode::c502_badGateway, "Bad Gateway"},
|
||||
{enet::HTTPAnswerCode::c503_serviceUnavailable, "Service Unavailable"},
|
||||
{enet::HTTPAnswerCode::c504_gatewayTimeout, "Gateway Timeout"},
|
||||
{enet::HTTPAnswerCode::c505_httpVersionNotSupported, "HTTP Version Not Supported"},
|
||||
{enet::HTTPAnswerCode::c511_networkAuthenticationRequired, "Network Authentication Required"}
|
||||
};
|
||||
|
||||
|
||||
|
||||
static std::map<int32_t, std::string> getErrorList() {
|
||||
static std::map<int32_t, std::string> g_list;
|
||||
return g_list;
|
||||
}
|
||||
|
||||
enet::Http::Http() :
|
||||
enet::Http::Http(enet::Tcp _connection, bool _isServer) :
|
||||
m_isServer(_isServer),
|
||||
m_connection(std::move(_connection)),
|
||||
m_headerIsSend(false),
|
||||
m_thread(nullptr),
|
||||
m_threadRunning(false),
|
||||
m_keepAlive(false) {
|
||||
m_connection.setPort(80);
|
||||
m_connection.setServer(false);
|
||||
setSendHeaderProperties("User-Agent", "e-net (ewol network interface)");
|
||||
if (m_keepAlive == true) {
|
||||
setSendHeaderProperties("Connection", "Keep-Alive");
|
||||
}
|
||||
}
|
||||
|
||||
enet::Http::~Http() {
|
||||
reset();
|
||||
stop();
|
||||
}
|
||||
|
||||
bool enet::Http::connect() {
|
||||
void enet::Http::threadCallback() {
|
||||
ENET_DEBUG("Start of thread HTTP");
|
||||
ethread::setName("TcpString-input");
|
||||
// get datas:
|
||||
while ( m_threadRunning == true
|
||||
&& m_connection.getConnectionStatus() == enet::Tcp::status::link) {
|
||||
// READ section data:
|
||||
if (m_headerIsSend == false) {
|
||||
getHeader();
|
||||
m_headerIsSend = true;
|
||||
}
|
||||
m_temporaryBuffer.resize(67000);
|
||||
int32_t len = m_connection.read(&m_temporaryBuffer[0], m_temporaryBuffer.size());
|
||||
if (len > 0) {
|
||||
ENET_INFO("Call client with datas ...");
|
||||
if (m_observer != nullptr) {
|
||||
m_observer(*this, m_temporaryBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_threadRunning = false;
|
||||
ENET_DEBUG("End of thread HTTP");
|
||||
}
|
||||
|
||||
|
||||
void enet::Http::start() {
|
||||
ENET_DEBUG("connect [START]");
|
||||
m_threadRunning = true;
|
||||
m_thread = new std::thread([&](void *){ this->threadCallback();}, nullptr);
|
||||
if (m_thread == nullptr) {
|
||||
m_threadRunning = false;
|
||||
ENET_ERROR("creating callback thread!");
|
||||
return;
|
||||
}
|
||||
while ( m_threadRunning == true
|
||||
&& m_connection.getConnectionStatus() != enet::Tcp::status::link) {
|
||||
usleep(50000);
|
||||
}
|
||||
//ethread::setPriority(*m_receiveThread, -6);
|
||||
ENET_DEBUG("connect [STOP]");
|
||||
}
|
||||
|
||||
void enet::Http::stop(bool _inThreadStop){
|
||||
ENET_DEBUG("disconnect [START]");
|
||||
m_threadRunning = false;
|
||||
/*
|
||||
if (m_connection.getConnectionStatus() == enet::Tcp::status::link) {
|
||||
return true;
|
||||
uint32_t size = 0xFFFFFFFF;
|
||||
m_connection.write(&size, 4);
|
||||
}
|
||||
if (m_connection.link() == false) {
|
||||
ENET_ERROR("can not link to the socket...");
|
||||
return false;
|
||||
*/
|
||||
if (m_connection.getConnectionStatus() != enet::Tcp::status::unlink) {
|
||||
m_connection.unlink();
|
||||
}
|
||||
return true;
|
||||
if (_inThreadStop == false) {
|
||||
if (m_thread != nullptr) {
|
||||
m_thread->join();
|
||||
delete m_thread;
|
||||
m_thread = nullptr;
|
||||
}
|
||||
}
|
||||
ENET_DEBUG("disconnect [STOP]");
|
||||
}
|
||||
|
||||
void enet::Http::setSendHeaderProperties(const std::string& _key, const std::string& _val) {
|
||||
auto it = m_sendHeader.find(_key);
|
||||
if (it == m_sendHeader.end()) {
|
||||
m_sendHeader.insert(make_pair(_key, _val));
|
||||
auto it = m_header.m_map.find(_key);
|
||||
if (it == m_header.m_map.end()) {
|
||||
m_header.m_map.insert(make_pair(_key, _val));
|
||||
} else {
|
||||
it->second = _val;
|
||||
}
|
||||
@ -54,96 +163,68 @@ std::string enet::Http::getReceiveHeaderProperties(const std::string& _key) {
|
||||
ENET_TODO("get header key=" << _key);
|
||||
return "";
|
||||
}
|
||||
|
||||
bool enet::Http::reset() {
|
||||
if (m_connection.getConnectionStatus() != enet::Tcp::status::link) {
|
||||
m_connection.unlink();
|
||||
void enet::Http::writeAnswerHeader(enum enet::HTTPAnswerCode _value) {
|
||||
std::string out;
|
||||
out = "HTTP/1.1 ";
|
||||
out += etk::to_string(int32_t(_value));
|
||||
auto it = protocolName.find(_value);
|
||||
if (it == protocolName.end() ) {
|
||||
out += " ???";
|
||||
} else {
|
||||
out += " " + it->second;
|
||||
}
|
||||
m_receiveData.clear();
|
||||
m_sendHeader.clear();
|
||||
m_receiveHeader.clear();
|
||||
setSendHeaderProperties("User-Agent", "e-net (ewol network interface)");
|
||||
if (m_keepAlive == true) {
|
||||
setSendHeaderProperties("Connection", "Keep-Alive");
|
||||
}
|
||||
return true;
|
||||
out += "\r\n\r\n";
|
||||
ENET_WARNING("Write header :" << out);
|
||||
write(out, false);
|
||||
}
|
||||
|
||||
bool enet::Http::setServer(const std::string& _hostName) {
|
||||
// if change server ==> restart connection ...
|
||||
if (_hostName == m_connection.getHostName()) {
|
||||
return true;
|
||||
}
|
||||
reset();
|
||||
m_connection.setHostNane(_hostName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enet::Http::setPort(uint16_t _port) {
|
||||
// if change server ==> restart connection ...
|
||||
if (_port == m_connection.getPort()) {
|
||||
return true;
|
||||
}
|
||||
reset();
|
||||
m_connection.setPort(_port);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enet::Http::receiveData() {
|
||||
std::string header;
|
||||
// Get data
|
||||
char data[1025];
|
||||
int32_t len = 1;
|
||||
void enet::Http::getHeader() {
|
||||
ENET_VERBOSE("Read HTTP Header [START]");
|
||||
bool headerEnded = false;
|
||||
while ( m_connection.getConnectionStatus() == enet::Tcp::status::link
|
||||
&& len > 0) {
|
||||
len = m_connection.read(data, 1024);
|
||||
// TODO : Parse header ...
|
||||
|
||||
if (headerEnded == false) {
|
||||
char previous = '\0';
|
||||
if (header.size()>0) {
|
||||
previous = header[header.size()-1];
|
||||
}
|
||||
for (int32_t iii=0; iii<len; ++iii) {
|
||||
if (headerEnded == false) {
|
||||
if (data[iii] != '\r') {
|
||||
header += data[iii];
|
||||
if (data[iii] == '\n') {
|
||||
//ENET_VERBOSE("parse: '\\n'");
|
||||
if (previous == '\n') {
|
||||
//ENET_VERBOSE("End header");
|
||||
// Find end of header
|
||||
headerEnded = true;
|
||||
}
|
||||
previous = data[iii];
|
||||
} else {
|
||||
previous = data[iii];
|
||||
//ENET_VERBOSE("parse: '" << data[iii] << "'");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_receiveData.push_back(data[iii]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int32_t iii=0; iii<len; ++iii) {
|
||||
m_receiveData.push_back(data[iii]);
|
||||
}
|
||||
std::string header;
|
||||
while (m_connection.getConnectionStatus() == enet::Tcp::status::link) {
|
||||
char type;
|
||||
int32_t len = m_connection.read(&type, 1);
|
||||
if (len == 0) {
|
||||
usleep(1);
|
||||
continue;
|
||||
}
|
||||
header += type;
|
||||
if ( header.size() > 4
|
||||
&& header[header.size()-1] == '\n'
|
||||
&& header[header.size()-2] == '\r'
|
||||
&& header[header.size()-3] == '\n'
|
||||
&& header[header.size()-4] == '\r') {
|
||||
// Normal end case ...
|
||||
break;
|
||||
} else if ( header.size() > 2
|
||||
&& header[header.size()-1] == '\n'
|
||||
&& header[header.size()-2] == '\n') {
|
||||
// linux end case
|
||||
break;
|
||||
} else if ( header.size() > 2
|
||||
&& header[header.size()-1] == '\r'
|
||||
&& header[header.size()-2] == '\r') {
|
||||
// Mac end case
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_connection.getConnectionStatus() != enet::Tcp::status::link) {
|
||||
ENET_WARNING("server disconnected");
|
||||
return false;
|
||||
}
|
||||
ENET_VERBOSE("Read HTTP Header [STOP] : '" << header << "'");
|
||||
m_headerIsSend = true;
|
||||
// parse header :
|
||||
std::vector<std::string> list = etk::split(header, '\n');
|
||||
for (auto &it : list) {
|
||||
if ( it.size()>0
|
||||
&& it[it.size()-1] == '\r') {
|
||||
it.resize(it.size()-1);
|
||||
}
|
||||
}
|
||||
headerEnded = false;
|
||||
m_receiveHeader.clear();
|
||||
m_header.m_map.clear();
|
||||
for (auto element : list) {
|
||||
if (headerEnded == false) {
|
||||
header = element;
|
||||
headerEnded = true;
|
||||
m_header.setReq(element);
|
||||
} else {
|
||||
size_t found = element.find(":");
|
||||
if (found == std::string::npos) {
|
||||
@ -151,10 +232,10 @@ bool enet::Http::receiveData() {
|
||||
continue;
|
||||
}
|
||||
ENET_VERBOSE("header : key='" << std::string(element, 0, found) << "' value='" << std::string(element, found+2) << "'");
|
||||
m_receiveHeader.insert(make_pair(unEscapeChar(std::string(element, 0, found)), unEscapeChar(std::string(element, found+2))));
|
||||
m_header.m_map.insert(make_pair(unEscapeChar(std::string(element, 0, found)), unEscapeChar(std::string(element, found+2))));
|
||||
}
|
||||
}
|
||||
for (auto &it : m_receiveHeader) {
|
||||
for (auto &it : m_header.m_map) {
|
||||
if (it.first == "Connection") {
|
||||
if (it.second == "close") {
|
||||
ENET_DEBUG("connection closed by remote :");
|
||||
@ -164,67 +245,29 @@ bool enet::Http::receiveData() {
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
ENET_INFO("header : '" << header << "'");
|
||||
for (auto &it : m_receiveHeader) {
|
||||
ENET_INFO("header : key='" << it.first << "' value='" << it.second << "'");
|
||||
m_header.display();
|
||||
if (m_observerRequest != nullptr) {
|
||||
m_observerRequest(*this, m_header);
|
||||
}
|
||||
*/
|
||||
// parse base answear:
|
||||
list = etk::split(header, ' ');
|
||||
if (list.size() < 2) {
|
||||
ENET_ERROR("can not parse answear : " << list);
|
||||
return false;
|
||||
}
|
||||
int32_t ret = etk::string_to_int32_t(list[1]);
|
||||
switch (ret/100) {
|
||||
case 1:
|
||||
// information message
|
||||
return true;
|
||||
break;
|
||||
case 2:
|
||||
// OK
|
||||
return true;
|
||||
break;
|
||||
case 3:
|
||||
// Redirect
|
||||
ENET_WARNING("Rediret request");
|
||||
return false;
|
||||
break;
|
||||
case 4:
|
||||
// client Error
|
||||
ENET_WARNING("Client error");
|
||||
return false;
|
||||
break;
|
||||
case 5:
|
||||
// server error
|
||||
ENET_WARNING("Server error");
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool enet::Http::get(const std::string& _address) {
|
||||
m_receiveData.clear();
|
||||
m_receiveHeader.clear();
|
||||
if (connect() == false) {
|
||||
return false;
|
||||
}
|
||||
std::string req = "GET http://" + m_connection.getHostName();
|
||||
m_header.m_map.clear();
|
||||
std::string req = "GET http://" + m_connection.getName();
|
||||
if (_address != "") {
|
||||
req += "/";
|
||||
req += _address;
|
||||
}
|
||||
req += " HTTP/1.0\n";
|
||||
req += " HTTP/1.1\r\n";
|
||||
setSendHeaderProperties("Content-Length", "0");
|
||||
// add header properties :
|
||||
for (auto &it : m_sendHeader) {
|
||||
req += escapeChar(it.first) + ": " + escapeChar(it.second) + "\n";
|
||||
for (auto &it : m_header.m_map) {
|
||||
req += escapeChar(it.first) + ": " + escapeChar(it.second) + "\r\n";
|
||||
}
|
||||
// end of header
|
||||
req += "\n";
|
||||
req += "\r\n";
|
||||
// no body:
|
||||
|
||||
int32_t len = m_connection.write(req, false);
|
||||
@ -233,7 +276,8 @@ bool enet::Http::get(const std::string& _address) {
|
||||
ENET_ERROR("An error occured when sending data " << len << "!=" << req.size());
|
||||
return false;
|
||||
}
|
||||
return receiveData();
|
||||
//return receiveData();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string enet::Http::escapeChar(const std::string& _value) {
|
||||
@ -245,10 +289,7 @@ std::string enet::Http::unEscapeChar(const std::string& _value) {
|
||||
|
||||
bool enet::Http::post(const std::string& _address, const std::map<std::string, std::string>& _values) {
|
||||
m_receiveData.clear();
|
||||
m_receiveHeader.clear();
|
||||
if (connect() == false) {
|
||||
return false;
|
||||
}
|
||||
m_header.m_map.clear();
|
||||
// First create body :
|
||||
std::string body;
|
||||
for (auto &it : _values) {
|
||||
@ -262,11 +303,8 @@ bool enet::Http::post(const std::string& _address, const std::map<std::string, s
|
||||
|
||||
bool enet::Http::post(const std::string& _address, const std::string& _contentType, const std::string& _data) {
|
||||
m_receiveData.clear();
|
||||
m_receiveHeader.clear();
|
||||
if (connect() == false) {
|
||||
return false;
|
||||
}
|
||||
std::string req = "POST http://" + m_connection.getHostName();
|
||||
m_header.m_map.clear();
|
||||
std::string req = "POST http://" + m_connection.getName();
|
||||
if (_address != "") {
|
||||
req += "/";
|
||||
req += _address;
|
||||
@ -275,8 +313,8 @@ bool enet::Http::post(const std::string& _address, const std::string& _contentTy
|
||||
setSendHeaderProperties("Content-Type", _contentType);
|
||||
setSendHeaderProperties("Content-Length", etk::to_string(_data.size()));
|
||||
// add header properties :
|
||||
for (auto &it : m_sendHeader) {
|
||||
req += escapeChar(it.first) + ": " + escapeChar(it.second) + "\n";
|
||||
for (auto &it : m_header.m_map) {
|
||||
req += escapeChar(it.first) + ": " + escapeChar(it.second) + "\r\n";
|
||||
}
|
||||
// end of header
|
||||
req += "\n";
|
||||
@ -288,7 +326,8 @@ bool enet::Http::post(const std::string& _address, const std::string& _contentTy
|
||||
ENET_ERROR("An error occured when sending data " << len << "!=" << req.size());
|
||||
return false;
|
||||
}
|
||||
return receiveData();
|
||||
//return receiveData();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -302,5 +341,56 @@ std::string enet::Http::dataString() {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
#endif
|
||||
|
||||
int32_t enet::Http::write(const void* _data, int32_t _len) {
|
||||
return m_connection.write(_data, _len);
|
||||
}
|
||||
|
||||
void enet::HttpHeader::setReq(const std::string& _req) {
|
||||
// parse base answear:
|
||||
std::vector<std::string> list = etk::split(_req, ' ');
|
||||
if (list.size() < 2) {
|
||||
ENET_ERROR("can not parse answear : " << list);
|
||||
return;
|
||||
}
|
||||
m_req = list[0];
|
||||
m_what = list[1];
|
||||
if ( m_req == "GET"
|
||||
|| m_req == "POST") {
|
||||
// HTTP CALL
|
||||
|
||||
} else if (etk::start_with(m_req,"HTTP/")==true) {
|
||||
// HTTP answer
|
||||
int32_t ret = etk::string_to_int32_t(m_what);
|
||||
switch (ret/100) {
|
||||
case 1:
|
||||
// information message
|
||||
break;
|
||||
case 2:
|
||||
// OK
|
||||
break;
|
||||
case 3:
|
||||
// Redirect
|
||||
ENET_WARNING("Rediret request");
|
||||
break;
|
||||
case 4:
|
||||
// client Error
|
||||
ENET_WARNING("Client error");
|
||||
break;
|
||||
case 5:
|
||||
// server error
|
||||
ENET_WARNING("Server error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void enet::HttpHeader::display() const {
|
||||
ENET_INFO("header :");
|
||||
for (auto &it : m_map) {
|
||||
ENET_INFO(" key='" << it.first << "' value='" << it.second << "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
226
enet/Http.h
226
enet/Http.h
@ -8,17 +8,135 @@
|
||||
#include <enet/Tcp.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <ethread/tools.h>
|
||||
|
||||
#if 0
|
||||
namespace enet {
|
||||
enum class HTTPAnswerCode {
|
||||
//1xx: Information
|
||||
c100_continue = 100, //!< The server has received the request headers, and the client should proceed to send the request body
|
||||
c101_switchingProtocols, //!< The requester has asked the server to switch protocols
|
||||
c103_checkpoint, //!< Used in the resumable requests proposal to resume aborted PUT or POST requests
|
||||
//2xx: Successful
|
||||
c200_ok = 200, //!< The request is OK (this is the standard response for successful HTTP requests)
|
||||
c201_created, //!< The request has been fulfilled, and a new resource is created
|
||||
c202_accepted, //!< The request has been accepted for processing, but the processing has not been completed
|
||||
c203_nonAuthoritativeInformation, //!< The request has been successfully processed, but is returning information that may be from another source
|
||||
c204_noContent, //!< The request has been successfully processed, but is not returning any content
|
||||
c205_resetContent, //!< The request has been successfully processed, but is not returning any content, and requires that the requester reset the document view
|
||||
c206_partialContent, //!< The server is delivering only part of the resource due to a range header sent by the client
|
||||
//3xx: Redirection
|
||||
c300_multipleChoices = 300, //!< A link list. The user can select a link and go to that location. Maximum five addresses
|
||||
c301_movedPermanently, //!< The requested page has moved to a new URL
|
||||
c302_found, //!< The requested page has moved temporarily to a new URL
|
||||
c303_seeOther, //!< The requested page can be found under a different URL
|
||||
c304_notModified, //!< Indicates the requested page has not been modified since last requested
|
||||
c306_switchProxy, //!< No longer used
|
||||
c307_temporaryRedirect, //!< The requested page has moved temporarily to a new URL
|
||||
c308_resumeIncomplete, //!< Used in the resumable requests proposal to resume aborted PUT or POST requests
|
||||
//4xx: Client Error,
|
||||
c400_badRequest = 400, //!< The request cannot be fulfilled due to bad syntax
|
||||
c401_unauthorized, //!< The request was a legal request, but the server is refusing to respond to it. For use when authentication is possible but has failed or not yet been provided
|
||||
c402_paymentRequired, //!< Reserved for future use
|
||||
c403_forbidden, //!< The request was a legal request, but the server is refusing to respond to it
|
||||
c404_notFound, //!< The requested page could not be found but may be available again in the future
|
||||
c405_methodNotAllowed, //!< A request was made of a page using a request method not supported by that page
|
||||
c406_notAcceptable, //!< The server can only generate a response that is not accepted by the client
|
||||
c407_proxyAuthenticationRequired, //!< The client must first authenticate itself with the proxy
|
||||
c408_requestTimeout, //!< The server timed out waiting for the request
|
||||
c409_conflict, //!< The request could not be completed because of a conflict in the request
|
||||
c410_gone, //!< The requested page is no longer available
|
||||
c411_lengthRequired, //!< The "Content-Length" is not defined. The server will not accept the request without it
|
||||
c412_preconditionFailed, //!< The precondition given in the request evaluated to false by the server
|
||||
c413_requestEntityTooLarge, //!< The server will not accept the request, because the request entity is too large
|
||||
c414_requestURITooLong, //!< The server will not accept the request, because the URL is too long. Occurs when you convert a POST request to a GET request with a long query information
|
||||
c415_unsupportedMediaType, //!< The server will not accept the request, because the media type is not supported
|
||||
c416_requestedRangeNotSatisfiable, //!< The client has asked for a portion of the file, but the server cannot supply that portion
|
||||
c417_expectationFailed, //!< The server cannot meet the requirements of the Expect request-header field
|
||||
//5xx: Server Error
|
||||
c500_internalServerError = 500, //!< A generic error message, given when no more specific message is suitable
|
||||
c501_notImplemented, //!< The server either does not recognize the request method, or it lacks the ability to fulfill the request
|
||||
c502_badGateway, //!< The server was acting as a gateway or proxy and received an invalid response from the upstream server
|
||||
c503_serviceUnavailable, //!< The server is currently unavailable (overloaded or down)
|
||||
c504_gatewayTimeout, //!< The server was acting as a gateway or proxy and did not receive a timely response from the upstream server
|
||||
c505_httpVersionNotSupported, //!< The server does not support the HTTP protocol version used in the request
|
||||
c511_networkAuthenticationRequired, //!< The client needs to authenticate to gain network access
|
||||
};
|
||||
|
||||
enum class HTTPProtocol {
|
||||
http_1_0,
|
||||
http_1_1,
|
||||
};
|
||||
class HttpHeader {
|
||||
private:
|
||||
// key, val
|
||||
std::map<std::string, std::string> m_map;
|
||||
enum HTTPProtocol m_protocol;
|
||||
public:
|
||||
void addKey(const std::string& _key, const std::string& _value);
|
||||
void rmKey(const std::string& _key);
|
||||
std::string getKey(const std::string& _key);
|
||||
enum HTTPProtocol getProtocol() {
|
||||
return m_protocol;
|
||||
}
|
||||
void setProtocol(enum HTTPProtocol _protocol) {
|
||||
m_protocol = _protocol;
|
||||
}
|
||||
virtual ~HttpHeader() = default;
|
||||
};
|
||||
class HttpAnswer : public HttpHeader {
|
||||
private:
|
||||
|
||||
enet::HTTPAnswerCode m_what;
|
||||
int64_t m_messageSize; // parameter
|
||||
public:
|
||||
HttpAnswer();
|
||||
HttpAnswer(const std::string& _value);
|
||||
get(const std::string& _uri);
|
||||
setSize(int64_t _messageSize=-1);
|
||||
void display() const;
|
||||
std::string generate();
|
||||
};
|
||||
enum class HTTPReqType {
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
};
|
||||
class HttpRequest : public HttpHeader{
|
||||
private:
|
||||
// key, val
|
||||
std::map<std::string, std::string> m_parameters;
|
||||
enum HTTPReqType m_req;
|
||||
std::string m_uri;
|
||||
enum HTTPProtocol m_protocol;
|
||||
bool m_keepAlive;
|
||||
public:
|
||||
HttpRequest(enum HTTPReqType _type,
|
||||
void display() const;
|
||||
std::string generate();
|
||||
|
||||
};
|
||||
class Http {
|
||||
public:
|
||||
Http();
|
||||
Http(enet::Tcp _connection, bool _isServer=false);
|
||||
virtual ~Http();
|
||||
private:
|
||||
bool m_isServer;
|
||||
public:
|
||||
bool getServerState() {
|
||||
return m_isServer;
|
||||
}
|
||||
private:
|
||||
enet::Tcp m_connection;
|
||||
bool m_headerIsSend;
|
||||
std::thread* m_thread;
|
||||
bool m_threadRunning;
|
||||
std::vector<uint8_t> m_temporaryBuffer;
|
||||
private:
|
||||
bool m_keepAlive;
|
||||
void threadCallback();
|
||||
public:
|
||||
void setKeepAlive(bool _keepAlive) {
|
||||
m_keepAlive = true;
|
||||
@ -27,32 +145,104 @@ namespace enet {
|
||||
return m_keepAlive;
|
||||
}
|
||||
private:
|
||||
// key, val
|
||||
std::map<std::string, std::string> m_sendHeader;
|
||||
std::map<std::string, std::string> m_receiveHeader;
|
||||
|
||||
HttpHeader m_header;
|
||||
std::vector<uint8_t> m_receiveData;
|
||||
bool connect();
|
||||
bool reset();
|
||||
void getHeader();
|
||||
public:
|
||||
void start();
|
||||
void stop(bool _inThread=false);
|
||||
bool isAlive() {
|
||||
return m_connection.getConnectionStatus() == enet::Tcp::status::link;
|
||||
}
|
||||
void setSendHeaderProperties(const std::string& _key, const std::string& _val);
|
||||
std::string getSendHeaderProperties(const std::string& _key);
|
||||
std::string getReceiveHeaderProperties(const std::string& _key);
|
||||
bool setServer(const std::string& _hostName);
|
||||
bool setPort(uint16_t _port);
|
||||
bool get(const std::string& _address);
|
||||
bool post(const std::string& _address, const std::map<std::string, std::string>& _values);
|
||||
bool post(const std::string& _address, const std::string& _contentType, const std::string& _data);
|
||||
|
||||
int32_t dataSize() {
|
||||
return m_receiveData.size();
|
||||
}
|
||||
const std::vector<uint8_t>& data() {
|
||||
return m_receiveData;
|
||||
}
|
||||
std::string dataString();
|
||||
void writeAnswerHeader(enum enet::HTTPAnswerCode _value);
|
||||
std::string escapeChar(const std::string& _value);
|
||||
std::string unEscapeChar(const std::string& _value);
|
||||
bool receiveData();
|
||||
public:
|
||||
using Observer = std::function<void(enet::Http& _interface, std::vector<uint8_t>&)>; //!< Define an Observer: function pointer
|
||||
Observer m_observer;
|
||||
/**
|
||||
* @brief Connect an function member on the signal with the shared_ptr object.
|
||||
* @param[in] _class shared_ptr Object on whe we need to call ==> the object is get in keeped in weak_ptr.
|
||||
* @param[in] _func Function to call.
|
||||
* @param[in] _args Argument optinnal the user want to add.
|
||||
*/
|
||||
template<class CLASS_TYPE>
|
||||
void connect(CLASS_TYPE* _class, void (CLASS_TYPE::*_func)(enet::Http& _interface, std::vector<uint8_t>&)) {
|
||||
m_observer = [=](enet::Http& _interface, std::vector<uint8_t>& _value){
|
||||
(*_class.*_func)(_interface,_value);
|
||||
};
|
||||
}
|
||||
void connect(Observer _func) {
|
||||
m_observer = _func;
|
||||
}
|
||||
public:
|
||||
using ObserverRequest = std::function<void(enet::Http& _interface, const enet::HttpHeader&)>; //!< Define an Observer: function pointer
|
||||
ObserverRequest m_observerRequest;
|
||||
/**
|
||||
* @brief Connect an function member on the signal with the shared_ptr object.
|
||||
* @param[in] _class shared_ptr Object on whe we need to call ==> the object is get in keeped in weak_ptr.
|
||||
* @param[in] _func Function to call.
|
||||
* @param[in] _args Argument optinnal the user want to add.
|
||||
*/
|
||||
template<class CLASS_TYPE>
|
||||
void connectHeader(CLASS_TYPE* _class, void (CLASS_TYPE::*_func)(enet::Http& _interface, const enet::HttpHeader&)) {
|
||||
m_observerRequest = [=](enet::Http& _interface, std::vector<uint8_t>& _value){
|
||||
(*_class.*_func)(_value);
|
||||
};
|
||||
}
|
||||
void connectHeader(ObserverRequest _func) {
|
||||
m_observerRequest = _func;
|
||||
}
|
||||
/**
|
||||
* @brief Write a chunk of data on the socket
|
||||
* @param[in] _data pointer on the data might be write
|
||||
* @param[in] _len Size that must be written socket
|
||||
* @return >0 byte size on the socket write
|
||||
* @return -1 an error occured.
|
||||
*/
|
||||
int32_t write(const void* _data, int32_t _len);
|
||||
/**
|
||||
* @brief Write a chunk of data on the socket
|
||||
* @param[in] _data String to rite on the soccket
|
||||
* @param[in] _writeBackSlashZero if false, the \0 is not write
|
||||
* @return >0 byte size on the socket write
|
||||
* @return -1 an error occured.
|
||||
*/
|
||||
int32_t write(const std::string& _data, bool _writeBackSlashZero = true) {
|
||||
if (_data.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (_writeBackSlashZero == true) {
|
||||
return write(_data.c_str(), _data.size()+1);
|
||||
}
|
||||
return write(_data.c_str(), _data.size());
|
||||
}
|
||||
/**
|
||||
* @brief Write a chunk of data on the socket
|
||||
* @param[in] _data String to rite on the soccket
|
||||
* @param[in] _writeBackSlashZero if false, the \0 is not write
|
||||
* @return >0 T element write on the socket
|
||||
* @return -1 an error occured.
|
||||
*/
|
||||
template <class T>
|
||||
int32_t write(const std::vector<T>& _data) {
|
||||
if (_data.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
size_t ret = write(&_data[0], _data.size()*sizeof(T));
|
||||
if (ret <=0) {
|
||||
return ret;
|
||||
}
|
||||
return ret/sizeof(T);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -81,7 +81,6 @@ bool enet::Tcp::unlink() {
|
||||
|
||||
|
||||
int32_t enet::Tcp::read(void* _data, int32_t _maxLen) {
|
||||
ENET_VERBOSE("read [START]");
|
||||
if (m_status != status::link) {
|
||||
ENET_ERROR("Can not read on unlink connection");
|
||||
return -1;
|
||||
@ -91,7 +90,6 @@ int32_t enet::Tcp::read(void* _data, int32_t _maxLen) {
|
||||
// Initialize the timeout to 3 minutes. If no activity after 3 minutes this program will end. timeout value is based on milliseconds.
|
||||
int timeout = (3 * 60 * 1000);
|
||||
// Call poll() and wait 3 minutes for it to complete.
|
||||
ENET_VERBOSE("Waiting on poll()...");
|
||||
int rc = poll(m_fds, nfds, timeout);
|
||||
// Check to see if the poll call failed.
|
||||
if (rc < 0) {
|
||||
@ -116,20 +114,18 @@ int32_t enet::Tcp::read(void* _data, int32_t _maxLen) {
|
||||
}
|
||||
// Check to see if the connection has been closed by the client
|
||||
if (rc == 0) {
|
||||
ENET_ERROR(" Connection closed");
|
||||
ENET_INFO(" Connection closed");
|
||||
closeConn = true;
|
||||
}
|
||||
if (closeConn == false) {
|
||||
// Data was received
|
||||
size = rc;
|
||||
ENET_VERBOSE(" " << size << " bytes received");
|
||||
} else {
|
||||
// If the close_conn flag was turned on, we need to clean up this active connection.
|
||||
// This clean up process includes removing the descriptor.
|
||||
ENET_ERROR(" Set status at remote close ...");
|
||||
ENET_DEBUG(" Set status at remote close ...");
|
||||
m_status = status::linkRemoteClose;
|
||||
}
|
||||
ENET_VERBOSE("read [STOP]");
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,7 @@ namespace enet {
|
||||
class Tcp {
|
||||
private:
|
||||
int32_t m_socketId; //!< socket linux interface generic
|
||||
#if 1
|
||||
struct pollfd m_fds[1];
|
||||
#endif
|
||||
struct pollfd m_fds[1];
|
||||
public:
|
||||
Tcp();
|
||||
Tcp(int32_t _idSocket, const std::string& _name);
|
||||
|
38
enet/WebSocket.cpp
Normal file
38
enet/WebSocket.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/** @file
|
||||
* @author Edouard DUPIN
|
||||
* @copyright 2014, Edouard DUPIN, all right reserved
|
||||
* @license APACHE v2.0 (see license file)
|
||||
*/
|
||||
|
||||
#include <enet/debug.h>
|
||||
#include <enet/WebSocket.h>
|
||||
#include <map>
|
||||
#include <etk/stdTools.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
enet::WebSocket::WebSocket(enet::Tcp _connection, bool _isServer) :
|
||||
m_interface(std::move(_connection), _isServer) {
|
||||
|
||||
}
|
||||
|
||||
enet::WebSocket::~WebSocket() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
void enet::WebSocket::start(const std::string& _uri) {
|
||||
if (m_interface.isServer() == true) {
|
||||
|
||||
}
|
||||
m_interface.start();
|
||||
if (m_interface.isServer() == false) {
|
||||
m_interface.get(
|
||||
}
|
||||
}
|
||||
|
||||
void enet::WebSocket::stop(bool _inThread=false) {
|
||||
|
||||
}
|
||||
|
22
enet/WebSocket.h
Normal file
22
enet/WebSocket.h
Normal file
@ -0,0 +1,22 @@
|
||||
/** @file
|
||||
* @author Edouard DUPIN
|
||||
* @copyright 2014, Edouard DUPIN, all right reserved
|
||||
* @license APACHE v2.0 (see license file)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <enet/Tcp.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace enet {
|
||||
class WebSocket{
|
||||
private:
|
||||
enet::Http m_interface;
|
||||
public:
|
||||
WebSocket(enet::Tcp _connection, bool _isServer=false);
|
||||
virtual ~WebSocket();
|
||||
void start(const std::string& _uri);
|
||||
void stop(bool _inThread=false);
|
||||
};
|
||||
}
|
41
lutin_enet-test-server-http.py
Normal file
41
lutin_enet-test-server-http.py
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/python
|
||||
import lutin.module as module
|
||||
import lutin.tools as tools
|
||||
|
||||
|
||||
def get_type():
|
||||
return "BINARY"
|
||||
|
||||
def get_sub_type():
|
||||
return "TEST"
|
||||
|
||||
def get_desc():
|
||||
return "e-net TEST test software for enet"
|
||||
|
||||
def get_licence():
|
||||
return "APACHE-2"
|
||||
|
||||
def get_compagny_type():
|
||||
return "com"
|
||||
|
||||
def get_compagny_name():
|
||||
return "atria-soft"
|
||||
|
||||
def get_maintainer():
|
||||
return ["Mr DUPIN Edouard <yui.heero@gmail.com>"]
|
||||
|
||||
def create(target, module_name):
|
||||
my_module = module.Module(__file__, module_name, get_type())
|
||||
my_module.add_export_path(tools.get_current_path(__file__))
|
||||
my_module.add_module_depend(['enet', 'gtest', 'test-debug'])
|
||||
my_module.add_src_file([
|
||||
'test/main-server-http.cpp'
|
||||
])
|
||||
return my_module
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -6,10 +6,19 @@
|
||||
|
||||
#include <test-debug/debug.h>
|
||||
#include <enet/Tcp.h>
|
||||
#include <enet/TcpClient.h>
|
||||
#include <enet/Http.h>
|
||||
#include <etk/etk.h>
|
||||
|
||||
#include <etk/stdTools.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace appl {
|
||||
void onReceiveData(enet::Http& _interface, std::vector<uint8_t>& _data) {
|
||||
TEST_INFO("Receive Datas : " << _data.size() << " bytes");
|
||||
TEST_INFO("data:" << (char*)&_data[0] << "");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int _argc, const char *_argv[]) {
|
||||
etk::init(_argc, _argv);
|
||||
@ -27,35 +36,25 @@ int main(int _argc, const char *_argv[]) {
|
||||
TEST_INFO("== Test HTTP client ==");
|
||||
TEST_INFO("==================================");
|
||||
#ifndef __TARGET_OS__Windows
|
||||
// client mode ...
|
||||
enet::Http connection;
|
||||
connection.setServer("127.0.0.1");
|
||||
// connect on TCP server:
|
||||
enet::Tcp tcpConnection = std::move(enet::connectTcpClient("127.0.0.1", 12345));
|
||||
// TODO : Check if connection is valid ...
|
||||
|
||||
// Create a HTTP connection in Client mode
|
||||
enet::Http connection(std::move(tcpConnection), false);
|
||||
connection.setKeepAlive(true);
|
||||
TEST_INFO("----------------------------");
|
||||
TEST_INFO("GET data : ");
|
||||
if (connection.get("") == false) {
|
||||
TEST_ERROR("can not GET data...");
|
||||
return -1;
|
||||
}
|
||||
TEST_INFO("data : " << connection.dataString());
|
||||
// Set callbacks:
|
||||
connection.connect(appl::onReceiveData);
|
||||
|
||||
TEST_INFO("----------------------------");
|
||||
TEST_INFO("POST data : ");
|
||||
std::map<std::string, std::string> values;
|
||||
values.insert(std::make_pair<std::string, std::string>("plop", "valuePlop"));
|
||||
if (connection.post("", values) == false) {
|
||||
TEST_ERROR("can not POST data...");
|
||||
return -1;
|
||||
}
|
||||
TEST_INFO("data : " << connection.dataString());
|
||||
// start http connection (the actual state is just TCP start ...)
|
||||
connection.start();
|
||||
connection.get("plop.txt");
|
||||
|
||||
TEST_INFO("----------------------------");
|
||||
TEST_INFO("POST xml : ");
|
||||
if (connection.post("", /*"application/xml"*/ "text/xml; charset=utf-8", "<plop><string>value1</string></plop>") == false) {
|
||||
TEST_ERROR("can not POST XML data...");
|
||||
return -1;
|
||||
|
||||
while (connection.isAlive() == true) {
|
||||
usleep(100000);
|
||||
}
|
||||
TEST_INFO("data : " << connection.dataString());
|
||||
|
||||
#else
|
||||
TEST_CRITICAL("not implemented");
|
||||
#endif
|
||||
|
111
test/main-server-http.cpp
Normal file
111
test/main-server-http.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/** @file
|
||||
* @author Edouard DUPIN
|
||||
* @copyright 2014, Edouard DUPIN, all right reserved
|
||||
* @license APACHE v2.0 (see license file)
|
||||
*/
|
||||
|
||||
#include <test-debug/debug.h>
|
||||
#include <enet/Tcp.h>
|
||||
#include <enet/Http.h>
|
||||
#include <enet/TcpServer.h>
|
||||
#include <etk/etk.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <etk/stdTools.h>
|
||||
namespace appl {
|
||||
void onReceiveData(enet::Http& _interface, std::vector<uint8_t>& _data) {
|
||||
TEST_INFO("Receive Datas : " << _data.size() << " bytes");
|
||||
}
|
||||
void onReceiveHeader(enet::Http& _interface, const enet::HttpHeader& _data) {
|
||||
TEST_INFO("Receive Header data:");
|
||||
_data.display();
|
||||
if (_data.m_req == "GET") {
|
||||
if (_data.m_what == "http://127.0.0.1:12345/plop.txt") {
|
||||
_interface.writeAnswerHeader(enet::HTTPAnswerCode::c200_ok);
|
||||
std::string data = "<html><head></head></body>coucou</body></html>";
|
||||
_interface.write(data);
|
||||
_interface.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
_interface.writeAnswerHeader(enet::HTTPAnswerCode::c200_ok);
|
||||
_interface.stop();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int _argc, const char *_argv[]) {
|
||||
etk::init(_argc, _argv);
|
||||
for (int32_t iii=0; iii<_argc ; ++iii) {
|
||||
std::string data = _argv[iii];
|
||||
if ( data == "-h"
|
||||
|| data == "--help") {
|
||||
TEST_PRINT(etk::getApplicationName() << " - help : ");
|
||||
TEST_PRINT(" " << _argv[0] << " [options]");
|
||||
TEST_PRINT(" No options ...");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
TEST_INFO("==================================");
|
||||
TEST_INFO("== Test HTTP server ==");
|
||||
TEST_INFO("==================================");
|
||||
#ifndef __TARGET_OS__Windows
|
||||
//Wait on TCP connection:
|
||||
enet::TcpServer interface;
|
||||
// Configure server interface:
|
||||
interface.setHostNane("127.0.0.1");
|
||||
interface.setPort(12345);
|
||||
// Start listening ...
|
||||
interface.link();
|
||||
// Wait a new connection ..
|
||||
enet::Tcp tcpConnection = std::move(interface.waitNext());
|
||||
// Free Connected port
|
||||
interface.unlink();
|
||||
// TODO : Check if connection is valid ...
|
||||
|
||||
// Create a HTTP connection in Server mode
|
||||
enet::Http connection(std::move(tcpConnection), true);
|
||||
connection.setKeepAlive(true);
|
||||
// Set callbacks:
|
||||
connection.connect(appl::onReceiveData);
|
||||
connection.connectHeader(appl::onReceiveHeader);
|
||||
|
||||
// start http connection (the actual state is just TCP start ...)
|
||||
connection.start();
|
||||
|
||||
while (connection.isAlive() == true) {
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
TEST_INFO("----------------------------");
|
||||
TEST_INFO("GET data : ");
|
||||
if (connection.get("") == false) {
|
||||
TEST_ERROR("can not GET data...");
|
||||
return -1;
|
||||
}
|
||||
TEST_INFO("data : " << connection.dataString());
|
||||
|
||||
TEST_INFO("----------------------------");
|
||||
TEST_INFO("POST data : ");
|
||||
std::map<std::string, std::string> values;
|
||||
values.insert(std::make_pair<std::string, std::string>("plop", "valuePlop"));
|
||||
if (connection.post("", values) == false) {
|
||||
TEST_ERROR("can not POST data...");
|
||||
return -1;
|
||||
}
|
||||
TEST_INFO("data : " << connection.dataString());
|
||||
|
||||
TEST_INFO("----------------------------");
|
||||
TEST_INFO("POST xml : ");
|
||||
if (connection.post("", "text/xml; charset=utf-8", "<plop><string>value1</string></plop>") == false) {
|
||||
TEST_ERROR("can not POST XML data...");
|
||||
return -1;
|
||||
}
|
||||
TEST_INFO("data : " << connection.dataString());
|
||||
*/
|
||||
#else
|
||||
TEST_CRITICAL("not implemented");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user