7 changed files with 3561 additions and 6 deletions
@ -0,0 +1,360 @@ |
|||||||
|
/*
|
||||||
|
* ZeroTier One - Network Virtualization Everywhere |
||||||
|
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
* -- |
||||||
|
* |
||||||
|
* You can be released from the requirements of the license by purchasing |
||||||
|
* a commercial license. Buying such a license is mandatory as soon as you |
||||||
|
* develop commercial closed-source software that incorporates or links |
||||||
|
* directly against ZeroTier software without disclosing the source code |
||||||
|
* of your own application. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "LFDB.hpp" |
||||||
|
|
||||||
|
#include <thread> |
||||||
|
#include <iostream> |
||||||
|
#include <sstream> |
||||||
|
|
||||||
|
#include "../osdep/OSUtils.hpp" |
||||||
|
#include "../ext/cpp-httplib/httplib.h" |
||||||
|
|
||||||
|
namespace ZeroTier |
||||||
|
{ |
||||||
|
|
||||||
|
LFDB::LFDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path,const char *lfOwnerPrivate,const char *lfOwnerPublic,const char *lfNodeHost,int lfNodePort,bool storeOnlineState) : |
||||||
|
DB(nc,myId,path), |
||||||
|
_nc(nc), |
||||||
|
_myId(myId), |
||||||
|
_lfOwnerPrivate(lfOwnerPrivate), |
||||||
|
_lfOwnerPublic(lfOwnerPublic), |
||||||
|
_lfNodeHost(lfNodeHost), |
||||||
|
_lfNodePort(lfNodePort), |
||||||
|
_running(true), |
||||||
|
_ready(false), |
||||||
|
_storeOnlineState(storeOnlineState) |
||||||
|
{ |
||||||
|
_syncThread = std::thread([this]() { |
||||||
|
char controllerAddress[24]; |
||||||
|
_myId.address().toString(controllerAddress); |
||||||
|
|
||||||
|
httplib::Client htcli(_lfNodeHost.c_str(),_lfNodePort,600); |
||||||
|
while (_running) { |
||||||
|
std::ostringstream query; |
||||||
|
query |
||||||
|
<< '{' |
||||||
|
<< "\"Ranges\":[{" |
||||||
|
<< "\"Name\": \"com.zerotier.controller.lfdb:" << controllerAddress << "\"" |
||||||
|
<< "}]," |
||||||
|
<< "\"MaskingKey\":\"" << controllerAddress << "\"," |
||||||
|
<< "\"Owners\":[\"" << _lfOwnerPublic << "\"]," |
||||||
|
<< "\"Open\":true" |
||||||
|
<< '}'; |
||||||
|
auto resp = htcli.Post("/query",query.str(),"application/json"); |
||||||
|
if (resp->status == 200) { |
||||||
|
fprintf(stderr,"%d %s\n",resp->status,resp->body.c_str()); |
||||||
|
} else { |
||||||
|
fprintf(stderr,"ERROR: LFDB: %d from node: %s" ZT_EOL_S,resp->status,resp->body.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
_ready = true; |
||||||
|
|
||||||
|
for(int k=0;k<10;++k) { |
||||||
|
if (!_running) |
||||||
|
return; |
||||||
|
usleep(100000); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
LFDB::~LFDB() |
||||||
|
{ |
||||||
|
_running = false; |
||||||
|
_syncThread.join(); |
||||||
|
} |
||||||
|
|
||||||
|
bool LFDB::waitForReady() |
||||||
|
{ |
||||||
|
while (!_ready) |
||||||
|
usleep(10000); |
||||||
|
} |
||||||
|
|
||||||
|
bool LFDB::isReady() |
||||||
|
{ |
||||||
|
return (_ready); |
||||||
|
} |
||||||
|
|
||||||
|
void LFDB::save(nlohmann::json *orig,nlohmann::json &record) |
||||||
|
{ |
||||||
|
if (orig) { |
||||||
|
if (*orig != record) { |
||||||
|
record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1; |
||||||
|
} |
||||||
|
} else { |
||||||
|
record["revision"] = 1; |
||||||
|
} |
||||||
|
|
||||||
|
const std::string objtype = record["objtype"]; |
||||||
|
if (objtype == "network") { |
||||||
|
const uint64_t nwid = OSUtils::jsonIntHex(record["id"],0ULL); |
||||||
|
if (nwid) { |
||||||
|
nlohmann::json old; |
||||||
|
get(nwid,old); |
||||||
|
if ((!old.is_object())||(old != record)) { |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (objtype == "member") { |
||||||
|
const uint64_t nwid = OSUtils::jsonIntHex(record["nwid"],0ULL); |
||||||
|
const uint64_t id = OSUtils::jsonIntHex(record["id"],0ULL); |
||||||
|
if ((id)&&(nwid)) { |
||||||
|
nlohmann::json network,old; |
||||||
|
get(nwid,network,id,old); |
||||||
|
if ((!old.is_object())||(old != record)) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void LFDB::eraseNetwork(const uint64_t networkId) |
||||||
|
{ |
||||||
|
// TODO
|
||||||
|
} |
||||||
|
|
||||||
|
void LFDB::eraseMember(const uint64_t networkId,const uint64_t memberId) |
||||||
|
{ |
||||||
|
// TODO
|
||||||
|
} |
||||||
|
|
||||||
|
void LFDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) |
||||||
|
{ |
||||||
|
std::lock_guard<std::mutex> l(_state_l); |
||||||
|
auto nw = _state.find(networkId); |
||||||
|
if (nw != _state.end()) { |
||||||
|
auto m = nw->second.members.find(memberId); |
||||||
|
if (m != nw->second.members.end()) { |
||||||
|
m->second.lastOnlineTime = OSUtils::now(); |
||||||
|
if (physicalAddress) |
||||||
|
m->second.lastOnlineAddress = physicalAddress; |
||||||
|
m->second.lastOnlineDirty = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#if 0 |
||||||
|
FileDB::FileDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path) : |
||||||
|
DB(nc,myId,path), |
||||||
|
_networksPath(_path + ZT_PATH_SEPARATOR_S + "network"), |
||||||
|
_tracePath(_path + ZT_PATH_SEPARATOR_S + "trace"), |
||||||
|
_onlineChanged(false), |
||||||
|
_running(true) |
||||||
|
{ |
||||||
|
OSUtils::mkdir(_path.c_str()); |
||||||
|
OSUtils::lockDownFile(_path.c_str(),true); |
||||||
|
OSUtils::mkdir(_networksPath.c_str()); |
||||||
|
OSUtils::mkdir(_tracePath.c_str()); |
||||||
|
|
||||||
|
std::vector<std::string> networks(OSUtils::listDirectory(_networksPath.c_str(),false)); |
||||||
|
std::string buf; |
||||||
|
for(auto n=networks.begin();n!=networks.end();++n) { |
||||||
|
buf.clear(); |
||||||
|
if ((n->length() == 21)&&(OSUtils::readFile((_networksPath + ZT_PATH_SEPARATOR_S + *n).c_str(),buf))) { |
||||||
|
try { |
||||||
|
nlohmann::json network(OSUtils::jsonParse(buf)); |
||||||
|
const std::string nwids = network["id"]; |
||||||
|
if (nwids.length() == 16) { |
||||||
|
nlohmann::json nullJson; |
||||||
|
_networkChanged(nullJson,network,false); |
||||||
|
std::string membersPath(_networksPath + ZT_PATH_SEPARATOR_S + nwids + ZT_PATH_SEPARATOR_S "member"); |
||||||
|
std::vector<std::string> members(OSUtils::listDirectory(membersPath.c_str(),false)); |
||||||
|
for(auto m=members.begin();m!=members.end();++m) { |
||||||
|
buf.clear(); |
||||||
|
if ((m->length() == 15)&&(OSUtils::readFile((membersPath + ZT_PATH_SEPARATOR_S + *m).c_str(),buf))) { |
||||||
|
try { |
||||||
|
nlohmann::json member(OSUtils::jsonParse(buf)); |
||||||
|
const std::string addrs = member["id"]; |
||||||
|
if (addrs.length() == 10) { |
||||||
|
nlohmann::json nullJson2; |
||||||
|
_memberChanged(nullJson2,member,false); |
||||||
|
} |
||||||
|
} catch ( ... ) {} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} catch ( ... ) {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_onlineUpdateThread = std::thread([this]() { |
||||||
|
unsigned int cnt = 0; |
||||||
|
while (this->_running) { |
||||||
|
std::this_thread::sleep_for(std::chrono::microseconds(100)); |
||||||
|
if ((++cnt % 20) == 0) { // 5 seconds
|
||||||
|
std::lock_guard<std::mutex> l(this->_online_l); |
||||||
|
if (!this->_running) return; |
||||||
|
if (this->_onlineChanged) { |
||||||
|
char p[4096],atmp[64]; |
||||||
|
for(auto nw=this->_online.begin();nw!=this->_online.end();++nw) { |
||||||
|
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx-online.json",_networksPath.c_str(),(unsigned long long)nw->first); |
||||||
|
FILE *f = fopen(p,"wb"); |
||||||
|
if (f) { |
||||||
|
fprintf(f,"{"); |
||||||
|
const char *memberPrefix = ""; |
||||||
|
for(auto m=nw->second.begin();m!=nw->second.end();++m) { |
||||||
|
fprintf(f,"%s\"%.10llx\":{" ZT_EOL_S,memberPrefix,(unsigned long long)m->first); |
||||||
|
memberPrefix = ","; |
||||||
|
InetAddress lastAddr; |
||||||
|
const char *timestampPrefix = " "; |
||||||
|
int cnt = 0; |
||||||
|
for(auto ts=m->second.rbegin();ts!=m->second.rend();) { |
||||||
|
if (cnt < 25) { |
||||||
|
if (lastAddr != ts->second) { |
||||||
|
lastAddr = ts->second; |
||||||
|
fprintf(f,"%s\"%lld\":\"%s\"" ZT_EOL_S,timestampPrefix,(long long)ts->first,ts->second.toString(atmp)); |
||||||
|
timestampPrefix = ","; |
||||||
|
++cnt; |
||||||
|
++ts; |
||||||
|
} else { |
||||||
|
ts = std::map<int64_t,InetAddress>::reverse_iterator(m->second.erase(std::next(ts).base())); |
||||||
|
} |
||||||
|
} else { |
||||||
|
ts = std::map<int64_t,InetAddress>::reverse_iterator(m->second.erase(std::next(ts).base())); |
||||||
|
} |
||||||
|
} |
||||||
|
fprintf(f,"}"); |
||||||
|
} |
||||||
|
fprintf(f,"}" ZT_EOL_S); |
||||||
|
fclose(f); |
||||||
|
} |
||||||
|
} |
||||||
|
this->_onlineChanged = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
FileDB::~FileDB() |
||||||
|
{ |
||||||
|
try { |
||||||
|
_online_l.lock(); |
||||||
|
_running = false; |
||||||
|
_online_l.unlock(); |
||||||
|
_onlineUpdateThread.join(); |
||||||
|
} catch ( ... ) {} |
||||||
|
} |
||||||
|
|
||||||
|
bool FileDB::waitForReady() { return true; } |
||||||
|
bool FileDB::isReady() { return true; } |
||||||
|
|
||||||
|
void FileDB::save(nlohmann::json *orig,nlohmann::json &record) |
||||||
|
{ |
||||||
|
char p1[4096],p2[4096],pb[4096]; |
||||||
|
try { |
||||||
|
if (orig) { |
||||||
|
if (*orig != record) { |
||||||
|
record["revision"] = OSUtils::jsonInt(record["revision"],0ULL) + 1; |
||||||
|
} |
||||||
|
} else { |
||||||
|
record["revision"] = 1; |
||||||
|
} |
||||||
|
|
||||||
|
const std::string objtype = record["objtype"]; |
||||||
|
if (objtype == "network") { |
||||||
|
const uint64_t nwid = OSUtils::jsonIntHex(record["id"],0ULL); |
||||||
|
if (nwid) { |
||||||
|
nlohmann::json old; |
||||||
|
get(nwid,old); |
||||||
|
if ((!old.is_object())||(old != record)) { |
||||||
|
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),nwid); |
||||||
|
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) |
||||||
|
fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1); |
||||||
|
_networkChanged(old,record,true); |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (objtype == "member") { |
||||||
|
const uint64_t id = OSUtils::jsonIntHex(record["id"],0ULL); |
||||||
|
const uint64_t nwid = OSUtils::jsonIntHex(record["nwid"],0ULL); |
||||||
|
if ((id)&&(nwid)) { |
||||||
|
nlohmann::json network,old; |
||||||
|
get(nwid,network,id,old); |
||||||
|
if ((!old.is_object())||(old != record)) { |
||||||
|
OSUtils::ztsnprintf(pb,sizeof(pb),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member",_networksPath.c_str(),(unsigned long long)nwid); |
||||||
|
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%.10llx.json",pb,(unsigned long long)id); |
||||||
|
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) { |
||||||
|
OSUtils::ztsnprintf(p2,sizeof(p2),"%s" ZT_PATH_SEPARATOR_S "%.16llx",_networksPath.c_str(),(unsigned long long)nwid); |
||||||
|
OSUtils::mkdir(p2); |
||||||
|
OSUtils::mkdir(pb); |
||||||
|
if (!OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1))) |
||||||
|
fprintf(stderr,"WARNING: controller unable to write to path: %s" ZT_EOL_S,p1); |
||||||
|
} |
||||||
|
_memberChanged(old,record,true); |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (objtype == "trace") { |
||||||
|
const std::string id = record["id"]; |
||||||
|
if (id.length() > 0) { |
||||||
|
OSUtils::ztsnprintf(p1,sizeof(p1),"%s" ZT_PATH_SEPARATOR_S "%s.json",_tracePath.c_str(),id.c_str()); |
||||||
|
OSUtils::writeFile(p1,OSUtils::jsonDump(record,-1)); |
||||||
|
} |
||||||
|
} |
||||||
|
} catch ( ... ) {} // drop invalid records missing fields
|
||||||
|
} |
||||||
|
|
||||||
|
void FileDB::eraseNetwork(const uint64_t networkId) |
||||||
|
{ |
||||||
|
nlohmann::json network,nullJson; |
||||||
|
get(networkId,network); |
||||||
|
char p[16384]; |
||||||
|
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx.json",_networksPath.c_str(),networkId); |
||||||
|
OSUtils::rm(p); |
||||||
|
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx-online.json",_networksPath.c_str(),networkId); |
||||||
|
OSUtils::rm(p); |
||||||
|
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member",_networksPath.c_str(),(unsigned long long)networkId); |
||||||
|
OSUtils::rmDashRf(p); |
||||||
|
_networkChanged(network,nullJson,true); |
||||||
|
std::lock_guard<std::mutex> l(this->_online_l); |
||||||
|
this->_online.erase(networkId); |
||||||
|
this->_onlineChanged = true; |
||||||
|
} |
||||||
|
|
||||||
|
void FileDB::eraseMember(const uint64_t networkId,const uint64_t memberId) |
||||||
|
{ |
||||||
|
nlohmann::json network,member,nullJson; |
||||||
|
get(networkId,network); |
||||||
|
get(memberId,member); |
||||||
|
char p[4096]; |
||||||
|
OSUtils::ztsnprintf(p,sizeof(p),"%s" ZT_PATH_SEPARATOR_S "%.16llx" ZT_PATH_SEPARATOR_S "member" ZT_PATH_SEPARATOR_S "%.10llx.json",_networksPath.c_str(),networkId,memberId); |
||||||
|
OSUtils::rm(p); |
||||||
|
_memberChanged(member,nullJson,true); |
||||||
|
std::lock_guard<std::mutex> l(this->_online_l); |
||||||
|
this->_online[networkId].erase(memberId); |
||||||
|
this->_onlineChanged = true; |
||||||
|
} |
||||||
|
|
||||||
|
void FileDB::nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress) |
||||||
|
{ |
||||||
|
char mid[32],atmp[64]; |
||||||
|
OSUtils::ztsnprintf(mid,sizeof(mid),"%.10llx",(unsigned long long)memberId); |
||||||
|
physicalAddress.toString(atmp); |
||||||
|
std::lock_guard<std::mutex> l(this->_online_l); |
||||||
|
this->_online[networkId][memberId][OSUtils::now()] = physicalAddress; |
||||||
|
this->_onlineChanged = true; |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
} // namespace ZeroTier
|
||||||
@ -0,0 +1,97 @@ |
|||||||
|
/*
|
||||||
|
* ZeroTier One - Network Virtualization Everywhere |
||||||
|
* Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/
|
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
* -- |
||||||
|
* |
||||||
|
* You can be released from the requirements of the license by purchasing |
||||||
|
* a commercial license. Buying such a license is mandatory as soon as you |
||||||
|
* develop commercial closed-source software that incorporates or links |
||||||
|
* directly against ZeroTier software without disclosing the source code |
||||||
|
* of your own application. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef ZT_CONTROLLER_LFDB_HPP |
||||||
|
#define ZT_CONTROLLER_LFDB_HPP |
||||||
|
|
||||||
|
#include "DB.hpp" |
||||||
|
|
||||||
|
#include <mutex> |
||||||
|
#include <string> |
||||||
|
#include <unordered_map> |
||||||
|
#include <atomic> |
||||||
|
|
||||||
|
namespace ZeroTier { |
||||||
|
|
||||||
|
/**
|
||||||
|
* DB implementation for controller that stores data in LF |
||||||
|
*/ |
||||||
|
class LFDB : public DB |
||||||
|
{ |
||||||
|
public: |
||||||
|
/**
|
||||||
|
* @param nc Network controller |
||||||
|
* @param myId Identity of controller node (with secret) |
||||||
|
* @param path Base path for ZeroTier node itself |
||||||
|
* @param lfOwnerPrivate LF owner private in PEM format |
||||||
|
* @param lfOwnerPublic LF owner public in @base62 format |
||||||
|
* @param lfNodeHost LF node host |
||||||
|
* @param lfNodePort LF node http (not https) port |
||||||
|
* @param storeOnlineState If true, store online/offline state and IP info in LF (a lot of data, only for private networks!) |
||||||
|
*/ |
||||||
|
LFDB(EmbeddedNetworkController *const nc,const Identity &myId,const char *path,const char *lfOwnerPrivate,const char *lfOwnerPublic,const char *lfNodeHost,int lfNodePort,bool storeOnlineState); |
||||||
|
virtual ~LFDB(); |
||||||
|
|
||||||
|
virtual bool waitForReady(); |
||||||
|
virtual bool isReady(); |
||||||
|
virtual void save(nlohmann::json *orig,nlohmann::json &record); |
||||||
|
virtual void eraseNetwork(const uint64_t networkId); |
||||||
|
virtual void eraseMember(const uint64_t networkId,const uint64_t memberId); |
||||||
|
virtual void nodeIsOnline(const uint64_t networkId,const uint64_t memberId,const InetAddress &physicalAddress); |
||||||
|
|
||||||
|
protected: |
||||||
|
EmbeddedNetworkController *const _nc; |
||||||
|
const Identity _myId; |
||||||
|
|
||||||
|
std::string _lfOwnerPrivate; |
||||||
|
std::string _lfOwnerPublic; |
||||||
|
std::string _lfNodeHost; |
||||||
|
int _lfNodePort; |
||||||
|
|
||||||
|
struct _MemberState |
||||||
|
{ |
||||||
|
InetAddress lastOnlineAddress; |
||||||
|
int64_t lastOnlineTime; |
||||||
|
bool dirty; |
||||||
|
bool lastOnlineDirty; |
||||||
|
}; |
||||||
|
struct _NetworkState |
||||||
|
{ |
||||||
|
std::unordered_map<uint64_t,_MemberState> members; |
||||||
|
bool dirty; |
||||||
|
}; |
||||||
|
std::unordered_map<uint64_t,_NetworkState> _state; |
||||||
|
std::mutex _state_l; |
||||||
|
|
||||||
|
std::atomic_bool _running; |
||||||
|
std::atomic_bool _ready; |
||||||
|
std::thread _syncThread; |
||||||
|
bool _storeOnlineState; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace ZeroTier
|
||||||
|
|
||||||
|
#endif |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2017 yhirose |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
||||||
|
|
||||||
@ -0,0 +1,259 @@ |
|||||||
|
cpp-httplib |
||||||
|
=========== |
||||||
|
|
||||||
|
[](https://travis-ci.org/yhirose/cpp-httplib) |
||||||
|
[](https://ci.appveyor.com/project/yhirose/cpp-httplib) |
||||||
|
|
||||||
|
A C++ header-only cross platform HTTP/HTTPS library. |
||||||
|
|
||||||
|
It's extremely easy to setup. Just include **httplib.h** file in your code! |
||||||
|
|
||||||
|
Inspired by [Sinatra](http://www.sinatrarb.com/) and [express](https://github.com/visionmedia/express). |
||||||
|
|
||||||
|
Server Example |
||||||
|
-------------- |
||||||
|
|
||||||
|
```c++ |
||||||
|
#include <httplib.h> |
||||||
|
|
||||||
|
int main(void) |
||||||
|
{ |
||||||
|
using namespace httplib; |
||||||
|
|
||||||
|
Server svr; |
||||||
|
|
||||||
|
svr.Get("/hi", [](const Request& req, Response& res) { |
||||||
|
res.set_content("Hello World!", "text/plain"); |
||||||
|
}); |
||||||
|
|
||||||
|
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) { |
||||||
|
auto numbers = req.matches[1]; |
||||||
|
res.set_content(numbers, "text/plain"); |
||||||
|
}); |
||||||
|
|
||||||
|
svr.listen("localhost", 1234); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
`Post`, `Put`, `Delete` and `Options` methods are also supported. |
||||||
|
|
||||||
|
### Bind a socket to multiple interfaces and any available port |
||||||
|
|
||||||
|
```cpp |
||||||
|
int port = svr.bind_to_any_port("0.0.0.0"); |
||||||
|
svr.listen_after_bind(); |
||||||
|
``` |
||||||
|
|
||||||
|
### Method Chain |
||||||
|
|
||||||
|
```cpp |
||||||
|
svr.Get("/get", [](const auto& req, auto& res) { |
||||||
|
res.set_content("get", "text/plain"); |
||||||
|
}) |
||||||
|
.Post("/post", [](const auto& req, auto& res) { |
||||||
|
res.set_content(req.body(), "text/plain"); |
||||||
|
}) |
||||||
|
.listen("localhost", 1234); |
||||||
|
``` |
||||||
|
|
||||||
|
### Static File Server |
||||||
|
|
||||||
|
```cpp |
||||||
|
svr.set_base_dir("./www"); |
||||||
|
``` |
||||||
|
|
||||||
|
### Logging |
||||||
|
|
||||||
|
```cpp |
||||||
|
svr.set_logger([](const auto& req, const auto& res) { |
||||||
|
your_logger(req, res); |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
### Error Handler |
||||||
|
|
||||||
|
```cpp |
||||||
|
svr.set_error_handler([](const auto& req, auto& res) { |
||||||
|
const char* fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>"; |
||||||
|
char buf[BUFSIZ]; |
||||||
|
snprintf(buf, sizeof(buf), fmt, res.status); |
||||||
|
res.set_content(buf, "text/html"); |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
### 'multipart/form-data' POST data |
||||||
|
|
||||||
|
```cpp |
||||||
|
svr.Post("/multipart", [&](const auto& req, auto& res) { |
||||||
|
auto size = req.files.size(); |
||||||
|
auto ret = req.has_file("name1")); |
||||||
|
const auto& file = req.get_file_value("name1"); |
||||||
|
// file.filename; |
||||||
|
// file.content_type; |
||||||
|
auto body = req.body.substr(file.offset, file.length)); |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
Client Example |
||||||
|
-------------- |
||||||
|
|
||||||
|
### GET |
||||||
|
|
||||||
|
```c++ |
||||||
|
#include <httplib.h> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
int main(void) |
||||||
|
{ |
||||||
|
httplib::Client cli("localhost", 1234); |
||||||
|
|
||||||
|
auto res = cli.Get("/hi"); |
||||||
|
if (res && res->status == 200) { |
||||||
|
std::cout << res->body << std::endl; |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### GET with Content Receiver |
||||||
|
|
||||||
|
```c++ |
||||||
|
std::string body; |
||||||
|
auto res = cli.Get("/large-data", [&](const char *data, size_t len) { |
||||||
|
body.append(data, len); |
||||||
|
}); |
||||||
|
assert(res->body.empty()); |
||||||
|
``` |
||||||
|
|
||||||
|
### POST |
||||||
|
|
||||||
|
```c++ |
||||||
|
res = cli.Post("/post", "text", "text/plain"); |
||||||
|
res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); |
||||||
|
``` |
||||||
|
|
||||||
|
### POST with parameters |
||||||
|
|
||||||
|
```c++ |
||||||
|
httplib::Params params; |
||||||
|
params.emplace("name", "john"); |
||||||
|
params.emplace("note", "coder"); |
||||||
|
|
||||||
|
auto res = cli.Post("/post", params); |
||||||
|
``` |
||||||
|
or |
||||||
|
|
||||||
|
```c++ |
||||||
|
httplib::Params params{ |
||||||
|
{ "name", "john" }, |
||||||
|
{ "note", "coder" } |
||||||
|
}; |
||||||
|
|
||||||
|
auto res = cli.Post("/post", params); |
||||||
|
``` |
||||||
|
|
||||||
|
### PUT |
||||||
|
|
||||||
|
```c++ |
||||||
|
res = cli.Put("/resource/foo", "text", "text/plain"); |
||||||
|
``` |
||||||
|
|
||||||
|
### DELETE |
||||||
|
|
||||||
|
```c++ |
||||||
|
res = cli.Delete("/resource/foo"); |
||||||
|
``` |
||||||
|
|
||||||
|
### OPTIONS |
||||||
|
|
||||||
|
```c++ |
||||||
|
res = cli.Options("*"); |
||||||
|
res = cli.Options("/resource/foo"); |
||||||
|
``` |
||||||
|
|
||||||
|
### Connection Timeout |
||||||
|
|
||||||
|
```c++ |
||||||
|
httplib::Client cli("localhost", 8080, 5); // timeouts in 5 seconds |
||||||
|
``` |
||||||
|
### With Progress Callback |
||||||
|
|
||||||
|
```cpp |
||||||
|
httplib::Client client(url, port); |
||||||
|
|
||||||
|
// prints: 0 / 000 bytes => 50% complete |
||||||
|
std::shared_ptr<httplib::Response> res = |
||||||
|
cli.Get("/", [](uint64_t len, uint64_t total) { |
||||||
|
printf("%lld / %lld bytes => %d%% complete\n", |
||||||
|
len, total, |
||||||
|
(int)((len/total)*100)); |
||||||
|
return true; // return 'false' if you want to cancel the request. |
||||||
|
} |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
 |
||||||
|
|
||||||
|
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23). |
||||||
|
|
||||||
|
### Basic Authentication |
||||||
|
|
||||||
|
```cpp |
||||||
|
httplib::Client cli("httplib.org"); |
||||||
|
|
||||||
|
auto res = cli.Get("/basic-auth/hello/world", { |
||||||
|
httplib::make_basic_authentication_header("hello", "world") |
||||||
|
}); |
||||||
|
// res->status should be 200 |
||||||
|
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n". |
||||||
|
``` |
||||||
|
|
||||||
|
### Range |
||||||
|
|
||||||
|
```cpp |
||||||
|
httplib::Client cli("httpbin.org"); |
||||||
|
|
||||||
|
auto res = cli.Get("/range/32", { |
||||||
|
httplib::make_range_header(1, 10) // 'Range: bytes=1-10' |
||||||
|
}); |
||||||
|
// res->status should be 206. |
||||||
|
// res->body should be "bcdefghijk". |
||||||
|
``` |
||||||
|
|
||||||
|
OpenSSL Support |
||||||
|
--------------- |
||||||
|
|
||||||
|
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked. |
||||||
|
|
||||||
|
```c++ |
||||||
|
#define CPPHTTPLIB_OPENSSL_SUPPORT |
||||||
|
|
||||||
|
SSLServer svr("./cert.pem", "./key.pem"); |
||||||
|
|
||||||
|
SSLClient cli("localhost", 8080); |
||||||
|
cli.set_ca_cert_path("./ca-bundle.crt"); |
||||||
|
cli.enable_server_certificate_verification(true); |
||||||
|
``` |
||||||
|
|
||||||
|
Zlib Support |
||||||
|
------------ |
||||||
|
|
||||||
|
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. |
||||||
|
|
||||||
|
The server applies gzip compression to the following MIME type contents: |
||||||
|
|
||||||
|
* all text types |
||||||
|
* image/svg+xml |
||||||
|
* application/javascript |
||||||
|
* application/json |
||||||
|
* application/xml |
||||||
|
* application/xhtml+xml |
||||||
|
|
||||||
|
NOTE |
||||||
|
---- |
||||||
|
|
||||||
|
g++ 4.8 cannot build this library since `<regex>` in g++4.8 is [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions). |
||||||
|
|
||||||
|
License |
||||||
|
------- |
||||||
|
|
||||||
|
MIT license (© 2019 Yuji Hirose) |
||||||
Loading…
Reference in new issue