加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
http_client.hpp 18.32 KB
一键复制 编辑 原始数据 按行查看 历史
gaoqingfeng 提交于 2023-09-16 22:22 . update
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
#pragma once
/*
* http client for anet implementation
* anet ��http �ͻ���
* Copyright (C) 2019 - 2023 gavingqf(gavingqf@126.com)
*
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
#include <functional>
#include <utility>
#include "event_loop.hpp"
#include "client.hpp"
#include "log.h"
#include "http_info.hpp"
#include "anet.hpp"
#include "asio/detail/noncopyable.hpp"
#include "semaphore.hpp"
namespace anet {
namespace http {
// http require callback typedef.
typedef std::function<void (const std::string &res,httpStatus status)> httpReqCallback;
// http require struct
struct HttpReq {
// method
std::string method{ "" };
// path
std::string path{ "" };
// version
std::string version{ "" };
// content type
std::string contentType{ "" };
// content
std::string content{ "" };
// connection mode
std::string connectionMode{ "" };
// http status
httpStatus status{ httpStatus::NOT_FIND };
// parse http content.
bool parse(const std::string &content) {
// == post parse == //
auto pos = content.find(gHttpSpaceLine);
assert(pos != gHttpStrFindInvalid && "invalid content");
if (pos == gHttpStrFindInvalid) {
return false;
}
// req content.
this->content = std::move(std::string(&content[pos + gHttpSpaceLineLen]));
// try to parse method and body
std::vector<std::string> vecContents;
strSplits(content, gHttpCRLF, vecContents);
{// parse content type.
auto find = false;
for (size_t i = 0; i < vecContents.size(); i++) {
auto p = vecContents[i].find(gHttpContentTypeLine);
if (gHttpStrFindInvalid != p) {
this->contentType = std::move(std::string(&vecContents[i][p + strlen(gHttpContentTypeLine)]));
find = true;
break;
}
}
if (!find) {
this->contentType = gHttpContentTypeJson;
}
}
{// parse Connection flag.
auto find = false;
for (size_t i = 0; i < vecContents.size(); i++) {
auto p = vecContents[i].find(gHttpConnectionLine);
if (gHttpStrFindInvalid != p) {
this->connectionMode = std::move(std::string(&vecContents[i][p + strlen(gHttpConnectionLine)]));
find = true;
break;
}
}
if (!find) {
this->connectionMode = gHttpConnectionClose;
}
}
// parse the first data.
std::vector<std::string> vec;
strSplits(vecContents[0], " ", vec);
// build http parameter.
assert(vec.size() >= 3 && "parse http info error");
if (vec.size() < 3) {
return false;
}
// get version and status.
this->version = std::move(vec[0]);
this->status = httpStatus(std::atoi(vec[1].c_str()));
return true;
}
};
// http client response define.
typedef std::pair<httpStatus, std::string> response;
// http req vector.
typedef std::vector<std::pair<HttpReq, httpReqCallback>> VecHttpReq;
// http client codec.
class CHttpClientCodec final: public anet::tcp::ICodec {
public:
CHttpClientCodec() = default;
virtual ~CHttpClientCodec() = default;
public:
virtual int parsePacket(const char *data, int len) override {
std::string strData(data, len);
auto pos = strData.find(gHttpSpaceLine);
if (pos == gHttpStrFindInvalid) {
return anet::tcp::retNotComplete;
}
auto lenPos = strData.find(gHttpContentLenLine);
if (lenPos == gHttpStrFindInvalid) {
return anet::tcp::retError; // just close it.
}
// content len flag string.
std::string conLenString(&data[lenPos + gHttpContentLenLineLen]);
auto contentLen = std::atoi(conLenString.c_str());
if (len >= int(pos + gHttpSpaceLineLen + contentLen)) {
return int(pos + gHttpSpaceLineLen + contentLen);
} else {
return anet::tcp::retNotComplete;
}
}
};
// http client session.
class CHttpClientSession final: public anet::tcp::ISession {
public:
explicit CHttpClientSession(const VecHttpReq &req) :
m_vecReq(req), m_index(0), m_close(true) {
}
virtual ~CHttpClientSession() = default;
public:
virtual void onConnected(anet::tcp::connSharePtr conn) override {
m_close = false;
m_conn = conn;
// host name
auto &&host = std::string(conn->getRemoteAddr());
host += ":";
host += std::to_string(conn->getRemotePort());
for (size_t i = 0; i < m_vecReq.size(); i++) {
this->sendHttpReq(m_vecReq[i].first, host);
}
}
virtual void onRecv(const char *data, int len) override {
HttpReq httpReq;
auto ret = httpReq.parse(std::string(data, data + len));
assert(ret && "parse http content error");
if (!ret) {
return;
}
auto &&reqCallback = m_vecReq[m_index].second;
reqCallback(httpReq.content, httpReq.status);
}
virtual void onTerminate() override {
m_close = true;
}
virtual void release() override {
delete this;
}
bool isClose() const {
return m_close;
}
private:
// send http response.
void sendHttpReq(const HttpReq &httpReq,const std::string &host) {
std::string req;
req.reserve(m_sendBuffSize);
if (0 == stricmp(httpReq.method.c_str(), gHttpGetMethod)) {
req = gHttpGetMethod;
req += " ";
req += httpReq.path;
// content
if (!httpReq.content.empty()) {
req += "?";
req += httpReq.content;
}
req += " ";
req += httpReq.version;
} else if (0 == stricmp(httpReq.method.c_str(), gHttpPostMethod)) {
req = gHttpPostMethod;
req += " ";
req += httpReq.path;
req += " ";
req += httpReq.version;
}
req.append(gHttpCRLF, gHttpCRLFLen);
// Content type
if (!httpReq.contentType.empty()) {
req.append(gHttpContentTypeLine, strlen(gHttpContentTypeLine));
req.append(httpReq.contentType);
req.append(";");
req.append(gHttpCharsetUtf8);
req.append(gHttpCRLF, gHttpCRLFLen);
} else {
req.append(gHttpContentTypeLine, strlen(gHttpContentTypeLine));
req.append(gHttpContentTypeUrlEncoded);
req.append(gHttpCRLF, gHttpCRLFLen);
}
// connection close flag.
req.append(gHttpConnectionLine, strlen(gHttpConnectionLine));
req.append(gHttpConnectionClose, strlen(gHttpConnectionClose));
req.append(gHttpCRLF, gHttpCRLFLen);
// !! above http/1.1 must add "host" command.
if (0 == stricmp(httpReq.version.c_str(), gHttpVersion11) ||
0 == stricmp(httpReq.version.c_str(), gHttpVersion20)) {
req.append("Host:" + host);
req.append(gHttpCRLF, gHttpCRLFLen);
}
// for POST
if (0 == stricmp(httpReq.method.c_str(), gHttpPostMethod)) {
// content len
std::string dataLen(gHttpContentLenLine);
dataLen += std::to_string(httpReq.content.size());
req += dataLen;
req.append(gHttpSpaceLine, gHttpSpaceLineLen);
// content.
req += httpReq.content;
} else {
// space line
req.append(gHttpSpaceLine, gHttpSpaceLineLen);
}
this->send(req);
}
protected:
void send(const std::string &req) {
if (isClose()) return;
m_conn->send(req);
}
private:
anet::tcp::connSharePtr m_conn{nullptr};
VecHttpReq m_vecReq;
size_t m_index;
std::atomic_bool m_close{ true };
static const int m_sendBuffSize{ 1024 };
};
//
// http client for asynchronous mode.
//
class CHttpClient final: asio::noncopyable {
public:
explicit CHttpClient(anet::tcp::CEventLoop &loop) : m_client(loop) {}
virtual ~CHttpClient() = default;
public:
bool connect(const std::string &addr, unsigned short port) {
auto sess = new CHttpClientSession(m_vecHttpReq);
m_client.setSession(sess);
m_client.setPacketParser(new CHttpClientCodec());
return m_client.syncConnect(addr, port);
}
// async post.
bool async_post(const std::string &path,
const std::string &req, httpReqCallback &&call
) {
if (!checkPath(path)) {
return false;
}
HttpReq httpReq;
httpReq.content = req;
httpReq.method = gHttpPostMethod;
httpReq.contentType = gHttpContentTypeJson;
httpReq.version = gHttpVersion11;
httpReq.connectionMode = gHttpConnectionClose;
httpReq.path = path;
m_vecHttpReq.push_back({ httpReq,call });
return true;
}
bool async_post(const std::string &path, const std::string &req,
const std::string &contentType, const std::string &httpVersion,
httpReqCallback &&call
) {
if (!checkPath(path)) {
return false;
}
HttpReq httpReq;
httpReq.content = req;
httpReq.method = gHttpPostMethod;
httpReq.contentType = contentType;
httpReq.version = httpVersion;
httpReq.connectionMode = gHttpConnectionClose;
httpReq.path = path;
m_vecHttpReq.push_back({ httpReq,call });
return true;
}
// async get.
bool async_get(const std::string &path,
const std::string &req, httpReqCallback &&call
) {
if (!checkPath(path)) {
return false;
}
HttpReq httpReq;
httpReq.content = req;
httpReq.path = path;
httpReq.method = gHttpGetMethod;
httpReq.contentType = gHttpContentTypeUrlEncoded;
httpReq.version = gHttpVersion11;
httpReq.connectionMode = gHttpConnectionClose;
m_vecHttpReq.push_back({ httpReq,call });
return true;
}
bool async_get(const std::string &path, const std::string &req,
const std::string &contentType, const std::string &httpVersion,
httpReqCallback &&call
) {
if (!checkPath(path)) {
return false;
}
HttpReq httpReq;
httpReq.content = req;
httpReq.path = path;
httpReq.method = gHttpGetMethod;
httpReq.contentType = contentType;
httpReq.version = httpVersion;
httpReq.connectionMode = gHttpConnectionClose;
m_vecHttpReq.push_back({ httpReq,call });
return true;
}
protected:
bool checkPath(const std::string& path) {
for (size_t i = 0; i < m_vecHttpReq.size(); i++) {
if (m_vecHttpReq[i].first.path == path) {
return false;
}
}
return true;
}
private:
// TCP client
anet::tcp::CClient m_client;
// http req vector
VecHttpReq m_vecHttpReq;
};
// http synchronous session factory.
template<typename session>
class CHttpSyncSessionFactory final {
public:
CHttpSyncSessionFactory() : m_nextId(1) {}
~CHttpSyncSessionFactory() = default;
CHttpSyncSessionFactory(const CHttpSyncSessionFactory &rhs) = delete;
CHttpSyncSessionFactory operator=(const CHttpSyncSessionFactory &rhs) = delete;
typedef std::shared_ptr<session> sessionSharePtr;
typedef std::map<unsigned int, sessionSharePtr> Id2SessionPtr;
public:
// return share_ptr<session> object.
sessionSharePtr createSession() {
std::lock_guard<std::mutex> mu(m_lock);
// create session's share pointer.
unsigned int sessionId = m_nextId++;
auto sessSharePtr = std::make_shared<session>();
// set id.
sessSharePtr->setId(sessionId);
sessSharePtr->SetFactory(this);
// save it to map to keep the life.
m_sessions[sessSharePtr->getId()] = sessSharePtr;
return sessSharePtr;
}
// release session.
void ReleaseSession(const session *pSession) {
if (pSession == nullptr) {
return;
}
std::lock_guard<std::mutex> mu(m_lock);
m_sessions.erase(pSession->getId());
}
protected:
mutable std::mutex m_lock;
Id2SessionPtr m_sessions;
unsigned int m_nextId{ 1 };
};
// http synchronous session and its factory.
class CHttpClientSyncSession;
typedef CHttpSyncSessionFactory<CHttpClientSyncSession> httSyncSessionFactory;
//
// http client session for synchronous mode.
//
class CHttpClientSyncSession final : public anet::tcp::ISession,
public std::enable_shared_from_this<CHttpClientSyncSession> {
public:
CHttpClientSyncSession() = default;
virtual ~CHttpClientSyncSession() = default;
public:
// connected callback
virtual void onConnected(anet::tcp::connSharePtr conn) {
m_close = false;
m_conn = conn;
}
// recv data callback: data and its length.
virtual void onRecv(const char *data, int len) {
HttpReq httpReq;
auto ret = httpReq.parse(std::string(data, data + len));
if (ret) {
m_res.first = httpReq.status;
m_res.second = httpReq.content;
} else {
m_res.first = httpStatus::NOT_FIND;
m_res.second = "";
}
m_sem.signal();
m_signaled = true;
}
// connection terminate callback
virtual void onTerminate() {
if (!m_signaled) {
m_sem.signal();
m_signaled = true;
}
m_close = true;
}
// release callback
virtual void release() {
assert(m_factory != nullptr);
m_factory->ReleaseSession(this);
}
void SetFactory(httSyncSessionFactory *factory) {
assert(factory != nullptr && "invalid session factory.");
m_factory = factory;
}
// send http response.
void sendHttpReq(const HttpReq &httpReq, const std::string &host) {
std::string req;
req.reserve(m_sendBuffSize);
if (0 == stricmp(httpReq.method.c_str(), gHttpGetMethod)) {
req = gHttpGetMethod;
req += " ";
req += httpReq.path;
// content
if (!httpReq.content.empty()) {
req += "?";
req += httpReq.content;
}
req += " ";
req += httpReq.version;
} else if (0 == stricmp(httpReq.method.c_str(), gHttpPostMethod)) {
req = gHttpPostMethod;
req += " ";
req += httpReq.path;
req += " ";
req += httpReq.version;
}
req.append(gHttpCRLF, gHttpCRLFLen);
// Content type
if (!httpReq.contentType.empty()) {
req.append(gHttpContentTypeLine, strlen(gHttpContentTypeLine));
req.append(httpReq.contentType);
req.append(gHttpCRLF, gHttpCRLFLen);
} else {
req.append(gHttpContentTypeLine, strlen(gHttpContentTypeLine));
req.append(gHttpContentTypeUrlEncoded);
req.append(gHttpCRLF, gHttpCRLFLen);
}
// connection close flag.
req.append(gHttpConnectionLine, strlen(gHttpConnectionLine));
req.append(gHttpConnectionClose, strlen(gHttpConnectionClose));
req.append(gHttpCRLF, gHttpCRLFLen);
// !! above http/1.1 must add host command.
if (0 == stricmp(httpReq.version.c_str(), gHttpVersion11) ||
0 == stricmp(httpReq.version.c_str(), gHttpVersion20)) {
req.append("Host:" + host);
req.append(gHttpCRLF, gHttpCRLFLen);
}
// for POST
if (0 == stricmp(httpReq.method.c_str(), gHttpPostMethod)) {
// content len
std::string dataLen(gHttpContentLenLine);
dataLen += std::to_string(httpReq.content.size());
req += dataLen;
// space line
req.append(gHttpSpaceLine, gHttpSpaceLineLen);
// content.
req += httpReq.content;
} else {
// space line
req.append(gHttpSpaceLine, gHttpSpaceLineLen);
}
this->send(req);
}
// tcp connection.
auto getConn() {
return m_conn;
}
void close() {
if (m_close) return;
m_conn->close();
}
// wait for result.
response waitFor() {
auto timeOut = std::chrono::milliseconds{3000};
if (!m_sem.wait_for(timeOut)) {
return { httpStatus::NOT_FIND,"" };
} else {
return m_res;
}
}
void setId(unsigned int id) {
m_id = id;
}
unsigned int getId() const {
return m_id;
}
protected:
void send(const std::string &req) {
if (m_close) return;
m_conn->send(req);
}
private:
anet::tcp::connSharePtr m_conn{ nullptr };
std::atomic_bool m_close{ true };
std::atomic_bool m_signaled{ false };
// session factory.
httSyncSessionFactory *m_factory{ nullptr };
// sync semaphore.
anet::utils::CSemaphore m_sem{0};
// session id.
unsigned int m_id{ 0 };
// response pair.
response m_res{ httpStatus::NOT_FIND, "" };
static const int m_sendBuffSize{ 1024 };
};
// http client for synchronous mode.
// this class must set a httSyncSessionFactory to manager session.
class CHttpSyncClient final : asio::noncopyable {
public:
explicit CHttpSyncClient(httSyncSessionFactory *factory) :
m_sessionFactory(factory) {
}
virtual ~CHttpSyncClient() = default;
public:
// return post {Status, content} synchronously.
response post(const std::string &addr, const std::string &path, const std::string &req) {
// connect to remote server.
if (!syncConnect(addr)) {
return { httpStatus::NOT_FIND,"" };
}
// send requirement.
this->sendReq(path, req, gHttpPostMethod);
// wait for response return.
return m_session->waitFor();
}
// return get {Status, content} synchronously.
response get(const std::string &addr, const std::string &path, const std::string &req) {
// connect to remote server.
if (!syncConnect(addr)) {
return { httpStatus::NOT_FIND,"" };
}
// send requirement.
this->sendReq(path, req, gHttpGetMethod);
// wait for result.
return m_session->waitFor();
}
void close() {
if (m_session != nullptr) {
m_session->close();
m_session = nullptr;
}
}
private:
void sendReq(const std::string &path, const std::string &req, const char *method) {
HttpReq httpReq;
httpReq.content = req;
httpReq.method = method;
httpReq.contentType = gHttpContentTypeUrlEncoded;
httpReq.version = gHttpVersion11;
httpReq.connectionMode = gHttpConnectionClose;
httpReq.path = path;
// host command.
auto &&host = std::string(m_session->getConn()->getRemoteAddr());
host += ":";
host += std::to_string(m_session->getConn()->getRemotePort());
m_session->sendHttpReq(httpReq, host);
}
bool syncConnect(const std::string &addr) {
this->close(); // try to close before connection.
std::vector<std::string> vec;
strSplits(addr, ":", vec);
if (vec.size() < 2) {
return false;
}
// initialize client's codec and session.
m_client.setPacketParser(&m_codec);
m_session = m_sessionFactory->createSession();
m_client.setSession(m_session.get());
// try to connect remote server.
auto port = (unsigned short)std::atoi(vec[1].c_str());
return m_client.syncConnect(vec[0], port);
}
private:
// TCP client
anet::tcp::CClient m_client{ m_loop };
// Event loop.
anet::tcp::CEventLoop m_loop{ 1 };
// Client codec.
CHttpClientCodec m_codec;
// client session.
using httpSyncSessionPtr = std::shared_ptr<CHttpClientSyncSession>;
httpSyncSessionPtr m_session{ nullptr };
// session factory.
httSyncSessionFactory *m_sessionFactory{ nullptr };
};
}
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化