mirror of
https://github.com/drogonframework/drogon.git
synced 2025-08-10 00:01:14 -04:00
add HttpAppFramework class,not complete
This commit is contained in:
parent
977160b7fc
commit
ecfea6f713
@ -1,6 +1,6 @@
|
||||
cmake_minimum_required (VERSION 2.6)
|
||||
|
||||
project (DROGON C CXX)
|
||||
project (DROGON CXX)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -g -ggdb")
|
||||
|
||||
@ -14,27 +14,33 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -g -ggdb")
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fpermissive -g -ggdb")
|
||||
# MESSAGE(STATUS "new gcc")
|
||||
#endif()
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake_modules/)
|
||||
|
||||
find_package (jsoncpp REQUIRED)
|
||||
include_directories(${JSONCPP_INCLUDE_DIRS})
|
||||
link_directories(${JSONCPP_LIBRARY_DIRS})
|
||||
find_package (UUID)
|
||||
|
||||
if(UUID_FOUND)
|
||||
add_definitions(-DUSE_UUID)
|
||||
endif()
|
||||
add_subdirectory(trantor)
|
||||
|
||||
SET(CMAKE_INSTALL_PREFIX /usr/local/drogon)
|
||||
#INSTALL(FILES trantor.cfg DESTINATION conf)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/trantor ${PROJECT_SOURCE_DIR}/lib/inc)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/trantor ${PROJECT_SOURCE_DIR}/drogon/inc)
|
||||
add_subdirectory(examples)
|
||||
|
||||
aux_source_directory(${PROJECT_SOURCE_DIR}/drogon/src DIR_SRCS)
|
||||
aux_source_directory(${PROJECT_SOURCE_DIR}/lib/src DIR_SRCS)
|
||||
|
||||
ADD_LIBRARY(drogon ${DIR_SRCS})
|
||||
|
||||
SET(CMAKE_INSTALL_PREFIX /usr/local)
|
||||
|
||||
install(TARGETS drogon DESTINATION lib)
|
||||
|
||||
file(GLOB trantor_net_headers "${CMAKE_CURRENT_SOURCE_DIR}/lib/inc/*.h")
|
||||
install(FILES ${trantor_net_headers} DESTINATION include/drogon)
|
||||
|
||||
#add_library(db ${DIR_DB_SRCS})
|
||||
add_executable(drogon ${DIR_SRCS})
|
||||
add_dependencies(drogon trantor)
|
||||
|
||||
#if (version LESS 6.0.0)
|
||||
# MESSAGE(STATUS "old gcc...")
|
||||
# set (LIBCONFIG config++)
|
||||
#else()
|
||||
# MESSAGE(STATUS "new gcc...")
|
||||
# set (LIBCONFIG libconfig++)
|
||||
#endif()
|
||||
|
||||
target_link_libraries(drogon trantor pthread)
|
||||
#crypt dl m pqxx pq hiredis mongocxx bsoncxx jsoncpp ${LIBCONFIG})
|
||||
INSTALL(TARGETS drogon DESTINATION bin)
|
||||
#target_link_libraries(drogon trantor pthread)
|
||||
|
119
cmake_modules/FindUUID.cmake
Normal file
119
cmake_modules/FindUUID.cmake
Normal file
@ -0,0 +1,119 @@
|
||||
# - Try to find UUID
|
||||
# Once done this will define
|
||||
#
|
||||
# UUID_FOUND - system has UUID
|
||||
# UUID_INCLUDE_DIRS - the UUID include directory
|
||||
# UUID_LIBRARIES - Link these to use UUID
|
||||
# UUID_DEFINITIONS - Compiler switches required for using UUID
|
||||
#
|
||||
# Copyright (c) 2006 Andreas Schneider <mail@cynapses.org>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the New
|
||||
# BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
#
|
||||
|
||||
|
||||
if (UUID_LIBRARIES AND UUID_INCLUDE_DIRS)
|
||||
# in cache already
|
||||
set(UUID_FOUND TRUE)
|
||||
else (UUID_LIBRARIES AND UUID_INCLUDE_DIRS)
|
||||
find_path(UUID_INCLUDE_DIR
|
||||
NAMES
|
||||
uuid.h
|
||||
PATH_SUFFIXES
|
||||
uuid
|
||||
HINTS
|
||||
${UUID_DIR}/include
|
||||
$ENV{UUID_DIR}/include
|
||||
$ENV{UUID_DIR}
|
||||
${DELTA3D_EXT_DIR}/inc
|
||||
$ENV{DELTA_ROOT}/ext/inc
|
||||
$ENV{DELTA_ROOT}
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/usr/include/gdal
|
||||
/sw/include # Fink
|
||||
/opt/local/include # DarwinPorts
|
||||
/opt/csw/include # Blastwave
|
||||
/opt/include
|
||||
[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Environment;OSG_ROOT]/include
|
||||
/usr/freeware/include
|
||||
)
|
||||
|
||||
find_library(UUID_LIBRARY
|
||||
NAMES
|
||||
uuid ossp-uuid
|
||||
HINTS
|
||||
${UUID_DIR}/lib
|
||||
$ENV{UUID_DIR}/lib
|
||||
$ENV{UUID_DIR}
|
||||
${DELTA3D_EXT_DIR}/lib
|
||||
$ENV{DELTA_ROOT}/ext/lib
|
||||
$ENV{DELTA_ROOT}
|
||||
$ENV{OSG_ROOT}/lib
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
/sw/lib
|
||||
/opt/local/lib
|
||||
/opt/csw/lib
|
||||
/opt/lib
|
||||
/usr/freeware/lib64
|
||||
)
|
||||
|
||||
find_library(UUID_LIBRARY_DEBUG
|
||||
NAMES
|
||||
uuidd
|
||||
HINTS
|
||||
${UUID_DIR}/lib
|
||||
$ENV{UUID_DIR}/lib
|
||||
$ENV{UUID_DIR}
|
||||
${DELTA3D_EXT_DIR}/lib
|
||||
$ENV{DELTA_ROOT}/ext/lib
|
||||
$ENV{DELTA_ROOT}
|
||||
$ENV{OSG_ROOT}/lib
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
/sw/lib
|
||||
/opt/local/lib
|
||||
/opt/csw/lib
|
||||
/opt/lib
|
||||
/usr/freeware/lib64
|
||||
)
|
||||
|
||||
if (NOT UUID_LIBRARY AND BSD)
|
||||
set(UUID_LIBRARY "")
|
||||
endif(NOT UUID_LIBRARY AND BSD)
|
||||
|
||||
set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR})
|
||||
set(UUID_LIBRARIES ${UUID_LIBRARY})
|
||||
|
||||
if (UUID_INCLUDE_DIRS)
|
||||
if (BSD OR UUID_LIBRARIES)
|
||||
set(UUID_FOUND TRUE)
|
||||
endif (BSD OR UUID_LIBRARIES)
|
||||
endif (UUID_INCLUDE_DIRS)
|
||||
|
||||
if (UUID_FOUND)
|
||||
if (NOT UUID_FIND_QUIETLY)
|
||||
message(STATUS "Found UUID: ${UUID_LIBRARIES}")
|
||||
endif (NOT UUID_FIND_QUIETLY)
|
||||
else (UUID_FOUND)
|
||||
if (UUID_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "Could not find UUID")
|
||||
endif (UUID_FIND_REQUIRED)
|
||||
endif (UUID_FOUND)
|
||||
|
||||
# show the UUID_INCLUDE_DIRS and UUID_LIBRARIES variables only in the advanced view
|
||||
mark_as_advanced(UUID_INCLUDE_DIRS UUID_LIBRARIES)
|
||||
|
||||
endif (UUID_LIBRARIES AND UUID_INCLUDE_DIRS)
|
@ -1,41 +0,0 @@
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
LOG_DEBUG<<"test start";
|
||||
Logger::setLogLevel(Logger::TRACE);
|
||||
//EventLoopThread loopThread;
|
||||
EventLoop loop;
|
||||
//loopThread.run();
|
||||
#if USE_IPV6
|
||||
InetAddress addr(7676,true,true);
|
||||
#else
|
||||
InetAddress addr(7676);
|
||||
#endif
|
||||
TcpServer server(&loop,addr,"test");
|
||||
server.setRecvMessageCallback([](const TcpConnectionPtr &connectionPtr,MsgBuffer *buffer){
|
||||
//LOG_DEBUG<<"recv callback!";
|
||||
std::cout<<std::string(buffer->peek(),buffer->readableBytes());
|
||||
connectionPtr->send(buffer->peek(),buffer->readableBytes());
|
||||
buffer->retrieveAll();
|
||||
connectionPtr->forceClose();
|
||||
});
|
||||
server.setConnectionCallback([](const TcpConnectionPtr& connPtr){
|
||||
if(connPtr->connected())
|
||||
{
|
||||
LOG_DEBUG<<"New connection";
|
||||
}
|
||||
else if(connPtr->disconnected())
|
||||
{
|
||||
LOG_DEBUG<<"connection disconnected";
|
||||
}
|
||||
});
|
||||
server.setIoLoopNum(3);
|
||||
server.start();
|
||||
loop.loop();
|
||||
}
|
3
examples/CMakeLists.txt
Executable file
3
examples/CMakeLists.txt
Executable file
@ -0,0 +1,3 @@
|
||||
link_libraries(drogon trantor uuid pthread)
|
||||
AUX_SOURCE_DIRECTORY(static_link_example DIR_STATIC)
|
||||
add_executable(webapp ${DIR_STATIC})
|
6
examples/static_link_example/main.cc
Executable file
6
examples/static_link_example/main.cc
Executable file
@ -0,0 +1,6 @@
|
||||
#include <HttpAppFramework.h>
|
||||
int main()
|
||||
{
|
||||
drogon::HttpAppFramework framework("0.0.0.0",12345);
|
||||
framework.run();
|
||||
}
|
39
lib/inc/HttpAppFramework.h
Executable file
39
lib/inc/HttpAppFramework.h
Executable file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <HttpRequest.h>
|
||||
#include <HttpResponse.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
namespace drogon
|
||||
{
|
||||
class HttpAppFramework:public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
HttpAppFramework()= delete;
|
||||
HttpAppFramework(const std::string &ip,uint16_t port);
|
||||
void run();
|
||||
|
||||
private:
|
||||
|
||||
std::string _ip;
|
||||
uint16_t _port;
|
||||
void onAsyncRequest(const HttpRequest& req,std::function<void (HttpResponse &)>callback);
|
||||
void readSendFile(const std::string& filePath,const HttpRequest& req, HttpResponse* resp);
|
||||
#ifdef USE_UUID
|
||||
//if uuid package found,we can use a uuid string as session id;
|
||||
uint _sessionTimeout=0;
|
||||
#endif
|
||||
bool _enableLastModify=true;
|
||||
std::set<std::string> _fileTypeSet={"html","jpg"};
|
||||
std::string _rootPath;
|
||||
|
||||
|
||||
|
||||
//tool funs
|
||||
#ifdef USE_UUID
|
||||
std::string getUuid();
|
||||
std::string stringToHex(unsigned char* ptr, long long length);
|
||||
#endif
|
||||
};
|
||||
}
|
255
lib/src/HttpAppFramework.cc
Executable file
255
lib/src/HttpAppFramework.cc
Executable file
@ -0,0 +1,255 @@
|
||||
#include "HttpAppFramework.h"
|
||||
#include "HttpServer.h"
|
||||
#include <sys/stat.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#ifdef USE_UUID
|
||||
#include <uuid/uuid.h>
|
||||
#endif
|
||||
using namespace drogon;
|
||||
using namespace std::placeholders;
|
||||
HttpAppFramework::HttpAppFramework(const std::string &ip,uint16_t port):
|
||||
_ip(ip),
|
||||
_port(port)
|
||||
{
|
||||
|
||||
}
|
||||
void HttpAppFramework::run()
|
||||
{
|
||||
trantor::EventLoop loop;
|
||||
HttpServer httpServer(&loop,InetAddress(_ip,_port),"");
|
||||
httpServer.setHttpAsyncCallback(std::bind(&HttpAppFramework::onAsyncRequest,this,_1,_2));
|
||||
httpServer.start();
|
||||
loop.loop();
|
||||
}
|
||||
|
||||
void HttpAppFramework::onAsyncRequest(const HttpRequest& req,std::function<void (HttpResponse &)>callback)
|
||||
{
|
||||
std::cout << "Headers " << req.methodString() << " " << req.path() << std::endl;
|
||||
|
||||
#if 1
|
||||
const std::map<std::string, std::string>& headers = req.headers();
|
||||
for (std::map<std::string, std::string>::const_iterator it = headers.begin();
|
||||
it != headers.end();
|
||||
++it) {
|
||||
std::cout << it->first << ": " << it->second << std::endl;
|
||||
}
|
||||
|
||||
std::cout<<"cookies:"<<std::endl;
|
||||
auto cookies = req.cookies();
|
||||
for(auto it=cookies.begin();it!=cookies.end();++it)
|
||||
{
|
||||
std::cout<<it->first<<"="<<it->second<<std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
LOG_INFO << "http path=" << req.path();
|
||||
LOG_INFO << "query: " << req.query() ;
|
||||
#ifdef USE_UUID
|
||||
std::string session_id=req.getCookie("JSESSIONID");
|
||||
bool needSetJsessionid=false;
|
||||
if(_sessionTimeout>0)
|
||||
{
|
||||
if(session_id=="")
|
||||
{
|
||||
session_id=getUuid().c_str();
|
||||
needSetJsessionid=true;
|
||||
//_sessionMap.insert(session_id,std::make_shared< Session >(),_sessionTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if(_sessionMap.find(session_id)==false)
|
||||
// {
|
||||
// _sessionMap.insert(session_id,std::make_shared< Session >(),_sessionTimeout);
|
||||
// }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
std::string path = req.path().c_str();
|
||||
auto pos = path.rfind(".");
|
||||
if(pos != std::string::npos) {
|
||||
std::string filetype = path.substr(pos + 1, path.length());
|
||||
transform(filetype.begin(), filetype.end(), filetype.begin(), tolower);
|
||||
if(_fileTypeSet.find(filetype) != _fileTypeSet.end()) {
|
||||
LOG_INFO << "file query!";
|
||||
std::string filePath = _rootPath + path;
|
||||
HttpResponse resp(true);
|
||||
#ifdef USE_UUID
|
||||
if(needSetJsessionid)
|
||||
resp.addCookie("JSESSIONID",session_id);
|
||||
#endif
|
||||
// pick a Content-Type for the file
|
||||
if(filetype=="html") resp.setContentTypeCode(CT_TEXT_HTML);
|
||||
else if(filetype=="js") resp.setContentTypeCode(CT_APPLICATION_X_JAVASCRIPT);
|
||||
else if(filetype=="css") resp.setContentTypeCode(CT_TEXT_CSS);
|
||||
else if(filetype=="xml") resp.setContentTypeCode(CT_TEXT_XML);
|
||||
else if(filetype=="xsl") resp.setContentTypeCode(CT_TEXT_XSL);
|
||||
else if(filetype=="txt") resp.setContentTypeCode(CT_TEXT_PLAIN);
|
||||
else if(filetype=="svg") resp.setContentTypeCode(CT_IMAGE_SVG_XML);
|
||||
else if(filetype=="ttf") resp.setContentTypeCode(CT_APPLICATION_X_FONT_TRUETYPE);
|
||||
else if(filetype=="otf") resp.setContentTypeCode(CT_APPLICATION_X_FONT_OPENTYPE);
|
||||
else if(filetype=="woff2")resp.setContentTypeCode(CT_APPLICATION_FONT_WOFF2);
|
||||
else if(filetype=="woff") resp.setContentTypeCode(CT_APPLICATION_FONT_WOFF);
|
||||
else if(filetype=="eot") resp.setContentTypeCode(CT_APPLICATION_VND_MS_FONTOBJ);
|
||||
else if(filetype=="png") resp.setContentTypeCode(CT_IMAGE_PNG);
|
||||
else if(filetype=="jpg") resp.setContentTypeCode(CT_IMAGE_JPG);
|
||||
else if(filetype=="jpeg") resp.setContentTypeCode(CT_IMAGE_JPG);
|
||||
else if(filetype=="gif") resp.setContentTypeCode(CT_IMAGE_GIF);
|
||||
else if(filetype=="bmp") resp.setContentTypeCode(CT_IMAGE_BMP);
|
||||
else if(filetype=="ico") resp.setContentTypeCode(CT_IMAGE_XICON);
|
||||
else if(filetype=="icns") resp.setContentTypeCode(CT_IMAGE_ICNS);
|
||||
else resp.setContentTypeCode(CT_APPLICATION_OCTET_STREAM);
|
||||
|
||||
readSendFile(filePath,req, &resp);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
/*filters
|
||||
std::vector<std::string> filters=(*_filterMap)[req.path().c_str()];
|
||||
for(std::string filterClassName:filters)
|
||||
{
|
||||
TRObject *_obj=TRClassMap::newObject(filterClassName);
|
||||
HttpFilter *filter= dynamic_cast<HttpFilter*>(_obj);
|
||||
int filter_flag=0;
|
||||
if(filter)
|
||||
{
|
||||
LOG_INFO<<filterClassName<<".doFilter()";
|
||||
filter->setSession(_sessionMap[session_id]);
|
||||
filter->doFilter(req,[=,&filter_flag]( HttpResponse &resp){
|
||||
callback(resp);
|
||||
filter_flag=1;
|
||||
});
|
||||
if(filter_flag)
|
||||
return;
|
||||
}
|
||||
else
|
||||
LOG_INFO<<"can't find filter "<<filterClassName;
|
||||
}
|
||||
*/
|
||||
/*find controller
|
||||
std::string ctrlName = (*_controllerMap)[req.path().c_str()];
|
||||
|
||||
TRObject *_object = TRClassMap::newObject(ctrlName);
|
||||
|
||||
HttpController *controller = dynamic_cast<HttpController*>(_object);
|
||||
|
||||
if(controller) {
|
||||
controller->setSession(_sessionMap[session_id]);
|
||||
controller->setEnvironment(this);
|
||||
controller->handleHttpAsyncRequest(req, [=](HttpResponse& resp){
|
||||
if(needSetJsessionid)
|
||||
resp.addCookie("JSESSIONID",session_id);
|
||||
callback(resp);
|
||||
});
|
||||
delete controller;
|
||||
} else {
|
||||
|
||||
LOG_ERROR<<"can't find controller "<<ctrlName;
|
||||
*/
|
||||
HttpResponse resp(true);
|
||||
|
||||
resp.setStatusCode(HttpResponse::k404NotFound);
|
||||
//resp.setCloseConnection(true);
|
||||
#ifdef USE_UUID
|
||||
if(needSetJsessionid)
|
||||
resp.addCookie("JSESSIONID",session_id);
|
||||
#endif
|
||||
callback(resp);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
void HttpAppFramework::readSendFile(const std::string& filePath,const HttpRequest& req, HttpResponse* resp)
|
||||
{
|
||||
//If-Modified-Since: Wed Jun 15 08:57:30 2016 GMT
|
||||
std::ifstream infile(filePath, std::ifstream::binary);
|
||||
LOG_INFO << "send http file:" << filePath;
|
||||
if(!infile) {
|
||||
|
||||
resp->setStatusCode(HttpResponse::k404NotFound);
|
||||
resp->setCloseConnection(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(_enableLastModify)
|
||||
{
|
||||
struct stat fileStat;
|
||||
LOG_DEBUG<<"enabled LastModify";
|
||||
if(stat(filePath.c_str(),&fileStat)>=0)
|
||||
{
|
||||
std::cout<<"last modify time:"<<fileStat.st_mtime<<std::endl;
|
||||
struct tm tm1;
|
||||
gmtime_r(&fileStat.st_mtime,&tm1);
|
||||
char timeBuf[64];
|
||||
asctime_r(&tm1,timeBuf);
|
||||
std::string timeStr(timeBuf);
|
||||
std::string::size_type len=timeStr.length();
|
||||
std::string lastModified =timeStr.substr(0,len-1)+" GMT";
|
||||
|
||||
std::string modiStr=req.getHeader("If-Modified-Since");
|
||||
if(modiStr!=""&&modiStr==lastModified)
|
||||
{
|
||||
LOG_DEBUG<<"not Modified!";
|
||||
resp->setStatusCode(HttpResponse::k304NotModified);
|
||||
return;
|
||||
}
|
||||
resp->addHeader("Last-Modified",lastModified);
|
||||
|
||||
resp->addHeader("Expires","Thu, 01 Jan 1970 00:00:00 GMT");
|
||||
}
|
||||
}
|
||||
|
||||
std::streambuf * pbuf = infile.rdbuf();
|
||||
std::streamsize size = pbuf->pubseekoff(0,infile.end);
|
||||
pbuf->pubseekoff(0,infile.beg); // rewind
|
||||
char *contents = new char [size];
|
||||
pbuf->sgetn (contents,size);
|
||||
infile.close();
|
||||
std::string str(contents,size);
|
||||
|
||||
|
||||
|
||||
|
||||
resp->setStatusCode(HttpResponse::k200Ok);
|
||||
LOG_INFO << "file len:" << str.length();
|
||||
resp->setBody(str);
|
||||
delete contents;
|
||||
}
|
||||
|
||||
#ifdef USE_UUID
|
||||
std::string HttpAppFramework::getUuid()
|
||||
{
|
||||
uuid_t uu;
|
||||
uuid_generate(uu);
|
||||
return stringToHex(uu, 16);
|
||||
}
|
||||
|
||||
std::string HttpAppFramework::stringToHex(unsigned char* ptr, long long length)
|
||||
{
|
||||
std::string idString;
|
||||
for (long long i = 0; i < length; i++)
|
||||
{
|
||||
int value = (ptr[i] & 0xf0)>>4;
|
||||
if (value < 10)
|
||||
{
|
||||
idString.append(1, char(value + 48));
|
||||
} else
|
||||
{
|
||||
idString.append(1, char(value + 55));
|
||||
}
|
||||
|
||||
value = (ptr[i] & 0x0f);
|
||||
if (value < 10)
|
||||
{
|
||||
idString.append(1, char(value + 48));
|
||||
} else
|
||||
{
|
||||
idString.append(1, char(value + 55));
|
||||
}
|
||||
}
|
||||
return idString;
|
||||
}
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user