Merge pull request #47 from an-tao/dev

1. Modify the HttpClientImpl class to fix some bugs;
2. Support attachments download;
This commit is contained in:
An Tao 2019-01-28 18:49:45 +08:00 committed by GitHub
commit 8b61ce3bcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 261 additions and 137 deletions

View File

@ -103,7 +103,7 @@
"log_size_limit": 100000000,
//log_level:"DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN"
//The TRACE level is only valid when built in DEBUG mode.
"log_level": "DEBUG"
"log_level": "TRACE"
},
//run_as_daemon:false by default
"run_as_daemon": false,

View File

@ -1,4 +1,6 @@
#include "api_Attachment.h"
#include <fstream>
using namespace api;
//add definition of your processing function here
void Attachment::get(const HttpRequestPtr &req,
@ -12,7 +14,7 @@ void Attachment::upload(const HttpRequestPtr &req,
const std::function<void(const HttpResponsePtr &)> &callback)
{
MultiPartParser fileUpload;
if(fileUpload.parse(req)==0)
if (fileUpload.parse(req) == 0)
{
auto files = fileUpload.getFiles();
for (auto const &file : files)
@ -43,3 +45,10 @@ void Attachment::upload(const HttpRequestPtr &req,
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
}
void Attachment::download(const HttpRequestPtr &req,
const std::function<void(const HttpResponsePtr &)> &callback)
{
auto resp = HttpResponse::newFileResponse("./drogon.jpg");
callback(resp);
}

View File

@ -10,11 +10,14 @@ class Attachment : public drogon::HttpController<Attachment>
//use METHOD_ADD to add your custom processing function here;
METHOD_ADD(Attachment::get, "", Get); //Path will be '/api/attachment'
METHOD_ADD(Attachment::upload, "/upload", Post);
METHOD_ADD(Attachment::download, "/download", Get);
METHOD_LIST_END
//your declaration of processing function maybe like this:
void get(const HttpRequestPtr &req,
const std::function<void(const HttpResponsePtr &)> &callback);
void upload(const HttpRequestPtr &req,
const std::function<void(const HttpResponsePtr &)> &callback);
void download(const HttpRequestPtr &req,
const std::function<void(const HttpResponsePtr &)> &callback);
};
} // namespace api

View File

@ -104,7 +104,7 @@ int main()
drogon::HttpAppFramework::instance().setSSLFiles("server.pem", "server.pem");
drogon::HttpAppFramework::instance().addListener("0.0.0.0", 8849, true);
#endif
app().setThreadNum(4);
//app().setThreadNum(4);
// trantor::Logger::setLogLevel(trantor::Logger::TRACE);
//class function
app().registerHttpMethod("/api/v1/handle1/{1}/{2}/?p3={3}&p4={4}", &A::handle);

View File

@ -16,12 +16,18 @@
#include <drogon/drogon.h>
#include <trantor/net/EventLoopThread.h>
#include <trantor/net/TcpClient.h>
#include <mutex>
#include <future>
#include <unistd.h>
#define RESET "\033[0m"
#define RED "\033[31m" /* Red */
#define GREEN "\033[32m" /* Green */
using namespace drogon;
void outputGood(const HttpRequestPtr &req)
{
static int i = 0;
@ -33,7 +39,7 @@ void outputGood(const HttpRequestPtr &req)
<< " " << req->path() << RESET << std::endl;
}
}
void doTest(const HttpClientPtr &client)
void doTest(const HttpClientPtr &client, std::promise<int> &pro)
{
/// Post json
Json::Value json;
@ -515,24 +521,61 @@ void doTest(const HttpClientPtr &client)
exit(1);
}
});
/// Test attachment download
req = HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->setPath("/api/attachment/download");
client->sendRequest(req, [=, &pro](ReqResult result, const HttpResponsePtr &resp) {
if (result == ReqResult::Ok)
{
if (resp->getBody().length() == 51822)
{
outputGood(req);
pro.set_value(1);
}
else
{
LOG_DEBUG << resp->getBody().length();
LOG_ERROR << "Error!";
exit(1);
}
}
else
{
LOG_ERROR << "Error!";
exit(1);
}
});
}
int main()
int main(int argc, char *argv[])
{
trantor::EventLoopThread loop[2];
trantor::Logger::setLogLevel(trantor::Logger::LogLevel::DEBUG);
bool ever = false;
if (argc > 1 && std::string(argv[1]) == "-f")
ever = true;
loop[0].run();
loop[1].run();
// for (int i = 0; i < 100;i++)
{
auto client = HttpClient::newHttpClient("http://127.0.0.1:8848", loop[0].getLoop());
doTest(client);
#ifdef USE_OPENSSL
auto sslClient = HttpClient::newHttpClient("https://127.0.0.1:8849", loop[1].getLoop());
doTest(sslClient);
#endif
}
getchar();
do
{
std::promise<int> pro1;
auto client = HttpClient::newHttpClient("http://127.0.0.1:8848", loop[0].getLoop());
doTest(client, pro1);
#ifdef USE_OPENSSL
std::promise<int> pro2;
auto sslClient = HttpClient::newHttpClient("https://127.0.0.1:8849", loop[1].getLoop());
doTest(sslClient, pro2);
auto f2 = pro2.get_future();
f2.get();
#endif
auto f1 = pro1.get_future();
f1.get();
//LOG_DEBUG << sslClient.use_count();
} while (ever);
//getchar();
loop[0].getLoop()->quit();
loop[1].getLoop()->quit();
}

View File

@ -58,9 +58,8 @@ class HttpClient : public trantor::NonCopyable
* The response from the http server will be obtained
* in the callback function.
*/
virtual void sendRequest(const HttpRequestPtr &req,
const HttpReqCallback &callback) = 0;
virtual void sendRequest(const HttpRequestPtr &req, const HttpReqCallback &callback) = 0;
virtual void sendRequest(const HttpRequestPtr &req, HttpReqCallback &&callback) = 0;
/// Use ip and port to connect to server
/**
* If useSSL is set to true, the client will

View File

@ -92,6 +92,7 @@ class HttpResponse
static HttpResponsePtr newHttpJsonResponse(const Json::Value &data);
static HttpResponsePtr newHttpViewResponse(const std::string &viewName, const HttpViewData &data = HttpViewData());
static HttpResponsePtr newLocationRedirectResponse(const std::string &path);
static HttpResponsePtr newFileResponse(const std::string &fullPath, const std::string &attachmentFileName = "", bool asAttachment = true);
virtual ~HttpResponse() {}
};

View File

@ -395,6 +395,7 @@ void HttpAppFrameworkImpl::onWebsockDisconnect(const WebSocketConnectionPtr &wsC
void HttpAppFrameworkImpl::onConnection(const TcpConnectionPtr &conn)
{
static std::mutex mtx;
LOG_TRACE << "connect!!!" << _maxConnectionNum << " num=" << _connectionNum.load();
if (conn->connected())
{
if (_connectionNum.fetch_add(1) >= _maxConnectionNum)
@ -422,7 +423,10 @@ void HttpAppFrameworkImpl::onConnection(const TcpConnectionPtr &conn)
}
else
{
_connectionNum--;
if (_connectionNum-- == 0)
{
_connectionNum++;
}
if (_maxConnectionNumPerIP > 0)
{
std::lock_guard<std::mutex> lock(mtx);
@ -611,7 +615,6 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
callback(resp);
return;
}
std::shared_ptr<HttpResponseImpl> resp = std::make_shared<HttpResponseImpl>();
//find cached response
HttpResponsePtr cachedResp;
{
@ -629,12 +632,14 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
//check last modified time,rfc2616-14.25
//If-Modified-Since: Mon, 15 Oct 2018 06:26:33 GMT
std::string timeStr;
if (_enableLastModify)
{
if (cachedResp)
{
if (std::dynamic_pointer_cast<HttpResponseImpl>(cachedResp)->getHeaderBy("last-modified") == req->getHeaderBy("if-modified-since"))
{
std::shared_ptr<HttpResponseImpl> resp = std::make_shared<HttpResponseImpl>();
resp->setStatusCode(k304NotModified);
if (needSetJsessionid)
{
@ -653,7 +658,6 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
LOG_TRACE << "last modify time:" << fileStat.st_mtime;
struct tm tm1;
gmtime_r(&fileStat.st_mtime, &tm1);
std::string timeStr;
timeStr.resize(64);
auto len = strftime((char *)timeStr.data(), timeStr.size(), "%a, %d %b %Y %T GMT", &tm1);
timeStr.resize(len);
@ -661,6 +665,7 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
if (modiStr == timeStr && !modiStr.empty())
{
LOG_TRACE << "not Modified!";
std::shared_ptr<HttpResponseImpl> resp = std::make_shared<HttpResponseImpl>();
resp->setStatusCode(k304NotModified);
if (needSetJsessionid)
{
@ -669,8 +674,6 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
callback(resp);
return;
}
resp->addHeader("Last-Modified", timeStr);
resp->addHeader("Expires", "Thu, 01 Jan 1970 00:00:00 GMT");
}
}
}
@ -689,50 +692,25 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
callback(cachedResp);
return;
}
// 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);
auto resp = HttpResponse::newFileResponse(filePath, "", false);
if (!timeStr.empty())
{
resp->addHeader("Last-Modified", timeStr);
resp->addHeader("Expires", "Thu, 01 Jan 1970 00:00:00 GMT");
}
//cache the response for 5 seconds by default
if (_staticFilesCacheTime >= 0)
{
resp->setExpiredTime(_staticFilesCacheTime);
_responseCachingMap->insert(filePath, resp, resp->expiredTime(), [=]() {
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
_staticFilesCache.erase(filePath);
});
{
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
_staticFilesCache[filePath] = resp;
}
}
if (needSetJsessionid)
{
@ -756,55 +734,6 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, std::fu
_httpSimpleCtrlsRouter.route(req, std::move(callback), needSetJsessionid, std::move(sessionId));
}
void HttpAppFrameworkImpl::readSendFile(const std::string &filePath, const HttpRequestImplPtr &req, const HttpResponsePtr &resp)
{
std::ifstream infile(filePath, std::ifstream::binary);
LOG_TRACE << "send http file:" << filePath;
if (!infile)
{
resp->setStatusCode(k404NotFound);
resp->setCloseConnection(true);
return;
}
std::streambuf *pbuf = infile.rdbuf();
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
pbuf->pubseekoff(0, infile.beg); // rewind
if (_useSendfile &&
filesize > 1024 * 200)
//TODO : Is 200k an appropriate value? Or set it to be configurable
{
//The advantages of sendfile() can only be reflected in sending large files.
std::dynamic_pointer_cast<HttpResponseImpl>(resp)->setSendfile(filePath);
}
else
{
std::string str;
str.resize(filesize);
pbuf->sgetn(&str[0], filesize);
resp->setBody(std::move(str));
}
resp->setStatusCode(k200OK);
//cache the response for 5 seconds by default
if (_staticFilesCacheTime >= 0)
{
resp->setExpiredTime(_staticFilesCacheTime);
_responseCachingMap->insert(filePath, resp, resp->expiredTime(), [=]() {
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
_staticFilesCache.erase(filePath);
});
{
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
_staticFilesCache[filePath] = resp;
}
}
}
trantor::EventLoop *HttpAppFrameworkImpl::loop()
{
static trantor::EventLoop loop;
@ -813,8 +742,7 @@ trantor::EventLoop *HttpAppFrameworkImpl::loop()
HttpAppFramework &HttpAppFramework::instance()
{
static HttpAppFrameworkImpl _instance;
return _instance;
return HttpAppFrameworkImpl::instance();
}
HttpAppFramework::~HttpAppFramework()

View File

@ -119,6 +119,13 @@ class HttpAppFrameworkImpl : public HttpAppFramework
const std::string &name = "default") override;
#endif
inline static HttpAppFrameworkImpl &instance()
{
static HttpAppFrameworkImpl _instance;
return _instance;
}
bool useSendfile() { return _useSendfile; }
private:
virtual void registerHttpController(const std::string &pathPattern,
const internal::HttpBinderBasePtr &binder,
@ -131,7 +138,6 @@ class HttpAppFrameworkImpl : public HttpAppFramework
void onWebsockMessage(const WebSocketConnectionPtr &wsConnPtr, trantor::MsgBuffer *buffer);
void onWebsockDisconnect(const WebSocketConnectionPtr &wsConnPtr);
void onConnection(const TcpConnectionPtr &conn);
void readSendFile(const std::string &filePath, const HttpRequestImplPtr &req, const HttpResponsePtr &resp);
void addHttpPath(const std::string &path,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,

View File

@ -95,7 +95,15 @@ HttpClientImpl::~HttpClientImpl()
void HttpClientImpl::sendRequest(const drogon::HttpRequestPtr &req, const drogon::HttpReqCallback &callback)
{
auto thisPtr = shared_from_this();
_loop->runInLoop([thisPtr,req,callback]() {
_loop->runInLoop([thisPtr, req, callback]() {
thisPtr->sendRequestInLoop(req, callback);
});
}
void HttpClientImpl::sendRequest(const drogon::HttpRequestPtr &req, drogon::HttpReqCallback &&callback)
{
auto thisPtr = shared_from_this();
_loop->runInLoop([thisPtr, req, callback = std::move(callback)]() {
thisPtr->sendRequestInLoop(req, callback);
});
}
@ -139,10 +147,13 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
_tcpClient->enableSSL();
}
#endif
auto thisPtr = shared_from_this();
std::weak_ptr<HttpClientImpl> weakPtr = shared_from_this();
assert(_reqAndCallbacks.empty());
_reqAndCallbacks.push(std::make_pair(req, callback));
_tcpClient->setConnectionCallback([=](const trantor::TcpConnectionPtr &connPtr) {
_tcpClient->setConnectionCallback([weakPtr](const trantor::TcpConnectionPtr &connPtr) {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
if (connPtr->connected())
{
connPtr->setContext(HttpResponseParser(connPtr));
@ -156,24 +167,33 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
LOG_TRACE << "connection disconnect";
while (!(thisPtr->_reqAndCallbacks.empty()))
{
auto reqCallback = _reqAndCallbacks.front().second;
_reqAndCallbacks.pop();
auto reqCallback = thisPtr->_reqAndCallbacks.front().second;
thisPtr->_reqAndCallbacks.pop();
reqCallback(ReqResult::NetworkFailure, HttpResponse::newHttpResponse());
}
thisPtr->_tcpClient.reset();
}
});
_tcpClient->setConnectionErrorCallback([=]() {
_tcpClient->setConnectionErrorCallback([weakPtr]() {
auto thisPtr = weakPtr.lock();
if (!thisPtr)
return;
//can't connect to server
while (!(thisPtr->_reqAndCallbacks.empty()))
{
auto reqCallback = _reqAndCallbacks.front().second;
_reqAndCallbacks.pop();
auto reqCallback = thisPtr->_reqAndCallbacks.front().second;
thisPtr->_reqAndCallbacks.pop();
reqCallback(ReqResult::BadServerAddress, HttpResponse::newHttpResponse());
}
thisPtr->_tcpClient.reset();
});
_tcpClient->setMessageCallback(std::bind(&HttpClientImpl::onRecvMessage, shared_from_this(), _1, _2));
_tcpClient->setMessageCallback([weakPtr](const trantor::TcpConnectionPtr &connPtr, trantor::MsgBuffer *msg) {
auto thisPtr = weakPtr.lock();
if (thisPtr)
{
thisPtr->onRecvMessage(connPtr, msg);
}
});
_tcpClient->connect();
}
else
@ -194,7 +214,11 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
sendReq(connPtr, req);
}
}
_reqAndCallbacks.push(std::make_pair(req, callback));
auto thisPtr = shared_from_this();
_reqAndCallbacks.push(std::make_pair(req, [thisPtr, callback](ReqResult result, const HttpResponsePtr &response) {
//thisPtr.reset();
callback(result, response);
}));
}
}

View File

@ -27,6 +27,7 @@ class HttpClientImpl : public HttpClient, public std::enable_shared_from_this<Ht
HttpClientImpl(trantor::EventLoop *loop, const trantor::InetAddress &addr, bool useSSL = false);
HttpClientImpl(trantor::EventLoop *loop, const std::string &hostString);
virtual void sendRequest(const HttpRequestPtr &req, const HttpReqCallback &callback) override;
virtual void sendRequest(const HttpRequestPtr &req, HttpReqCallback &&callback) override;
~HttpClientImpl();
private:

View File

@ -12,15 +12,16 @@
*
*/
#include "HttpAppFrameworkImpl.h"
#include "HttpResponseImpl.h"
#include <drogon/HttpViewBase.h>
#include <drogon/HttpViewData.h>
#include <drogon/HttpAppFramework.h>
#include <trantor/utils/Logger.h>
#include <memory>
#include <fstream>
#include <stdio.h>
#include <sys/stat.h>
#include <memory>
using namespace trantor;
using namespace drogon;
@ -67,6 +68,114 @@ HttpResponsePtr HttpResponse::newHttpViewResponse(const std::string &viewName, c
return HttpViewBase::genHttpResponse(viewName, data);
}
HttpResponsePtr HttpResponse::newFileResponse(const std::string &fullPath, const std::string &fileNameForUser, bool asAttachment)
{
auto resp = std::make_shared<HttpResponseImpl>();
std::ifstream infile(fullPath, std::ifstream::binary);
LOG_TRACE << "send http file:" << fullPath;
if (!infile)
{
resp->setStatusCode(k404NotFound);
resp->setCloseConnection(true);
return resp;
}
std::streambuf *pbuf = infile.rdbuf();
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
pbuf->pubseekoff(0, infile.beg); // rewind
if (HttpAppFrameworkImpl::instance().useSendfile() &&
filesize > 1024 * 200)
//TODO : Is 200k an appropriate value? Or set it to be configurable
{
//The advantages of sendfile() can only be reflected in sending large files.
std::dynamic_pointer_cast<HttpResponseImpl>(resp)->setSendfile(fullPath);
}
else
{
std::string str;
str.resize(filesize);
pbuf->sgetn(&str[0], filesize);
resp->setBody(std::move(str));
}
resp->setStatusCode(k200OK);
std::string filename;
std::string filetype;
if (!fileNameForUser.empty())
{
filename = fileNameForUser;
}
else
{
auto pos = fullPath.rfind("/");
if (pos != std::string::npos)
{
filename = fullPath.substr(pos + 1);
}
else
{
filename = fullPath;
}
}
auto pos = filename.rfind(".");
if (pos != std::string::npos)
{
filetype = filename.substr(pos + 1);
transform(filetype.begin(), filetype.end(), filetype.begin(), tolower);
}
bool attachment = (asAttachment || (!fileNameForUser.empty()));
// 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);
attachment = true;
}
if (attachment)
{
resp->addHeader("Content-Disposition", "attachment; filename=" + filename);
}
return resp;
}
std::string HttpResponseImpl::web_response_code_to_string(int code)
{
switch (code)

View File

@ -82,13 +82,14 @@ void HttpServer::onConnection(const TcpConnectionPtr &conn)
{
LOG_TRACE << "conn disconnected!";
HttpRequestParser *requestParser = any_cast<HttpRequestParser>(conn->getMutableContext());
// LOG_INFO << "###:" << string(buf->peek(), buf->readableBytes());
if (requestParser->webSocketConn())
if (requestParser)
{
_disconnectWebsocketCallback(requestParser->webSocketConn());
if (requestParser->webSocketConn())
{
_disconnectWebsocketCallback(requestParser->webSocketConn());
}
conn->getMutableContext()->reset();
}
conn->getMutableContext()->reset();
}
_connectionCallback(conn);
}

@ -1 +1 @@
Subproject commit 1173464eb3eb521232c84a76dc98ac91fa34b9e5
Subproject commit 1904e558588ac67b8422d2c8a83166dff9506716