Skip to content

Commit

Permalink
add http-check and http-modify filters
Browse files Browse the repository at this point in the history
  • Loading branch information
jkarneges committed Dec 18, 2024
1 parent 8ed402d commit 5fc4533
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 8 deletions.
11 changes: 11 additions & 0 deletions src/core/packet/wscontrolpacket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ QVariant WsControlPacket::toVariant() const
if(item.logLevel >= 0)
vitem["log-level"] = item.logLevel;

if(item.trusted)
vitem["trusted"] = true;

if(!item.channel.isEmpty())
vitem["channel"] = item.channel;

Expand Down Expand Up @@ -395,6 +398,14 @@ bool WsControlPacket::fromVariant(const QVariant &in)
item.logLevel = vitem["log-level"].toInt();
}

if(vitem.contains("trusted"))
{
if(typeId(vitem["trusted"]) != QMetaType::Bool)
return false;

item.trusted = vitem["trusted"].toBool();
}

if(vitem.contains("channel"))
{
if(typeId(vitem["channel"]) != QMetaType::QByteArray)
Expand Down
2 changes: 2 additions & 0 deletions src/core/packet/wscontrolpacket.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class WsControlPacket
bool separateStats;
QByteArray channelPrefix;
int logLevel;
bool trusted;
QByteArray channel;
int ttl;
int timeout;
Expand All @@ -76,6 +77,7 @@ class WsControlPacket
code(-1),
separateStats(false),
logLevel(-1),
trusted(false),
ttl(-1),
timeout(-1)
{
Expand Down
202 changes: 201 additions & 1 deletion src/handler/filter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@

#include "filter.h"

#include <QJsonDocument>
#include <QJsonObject>
#include "log.h"
#include "format.h"
#include "idformat.h"
#include "zhttpmanager.h"
#include "zhttprequest.h"

namespace {

Expand Down Expand Up @@ -304,6 +308,192 @@ class VarSubstFilter : public Filter, public Filter::MessageFilter
}
};

class HttpFilter : public Filter::MessageFilter
{
public:
enum Mode
{
Check,
Modify
};

Mode mode;
std::unique_ptr<ZhttpRequest> req;
boost::signals2::scoped_connection readyReadConnection;
boost::signals2::scoped_connection errorConnection;
QByteArray origContent;
bool haveResponseHeader;
QByteArray responseBody;

HttpFilter(Mode _mode) :
mode(_mode),
haveResponseHeader(false)
{
}

virtual void start(const Filter::Context &context, const QByteArray &content)
{
QUrl url = QUrl(context.subscriptionMeta.value("url"), QUrl::StrictMode);
if(!url.isValid())
{
Result r;
r.errorMessage = "invalid or missing url value";
finished(r);
return;
}

QUrl currentUri = context.currentUri;
if(currentUri.scheme() == "wss")
currentUri.setScheme("https");
else if(currentUri.scheme() == "ws")
currentUri.setScheme("http");

QUrl destUri = currentUri.resolved(url);

origContent = content;

req.reset(context.zhttpOut->createRequest());
readyReadConnection = req->readyRead.connect(boost::bind(&HttpFilter::req_readyRead, this));
errorConnection = req->error.connect(boost::bind(&HttpFilter::req_error, this));

int currentPort = currentUri.port(currentUri.scheme() == "https" ? 443 : 80);
int destPort = destUri.port(destUri.scheme() == "https" ? 443 : 80);

QVariantHash passthroughData;

passthroughData["route"] = context.route.toUtf8();

// if dest link points to the same service as the current request,
// then we can assume the network would send the request back to
// us, so we can handle it internally. if the link points to a
// different service, then we can't make this assumption and need
// to make the request over the network. note that such a request
// could still end up looping back to us
if(destUri.scheme() == currentUri.scheme() && destUri.host() == currentUri.host() && destPort == currentPort)
{
// tell the proxy that we prefer the request to be handled
// internally, using the same route
passthroughData["prefer-internal"] = true;
}

// needed in case internal routing is not used
if(context.trusted)
passthroughData["trusted"] = true;

req->setPassthroughData(passthroughData);

HttpHeaders headers;

{
QVariantMap vmap;
QHashIterator<QString, QString> it(context.subscriptionMeta);
while(it.hasNext())
{
it.next();
vmap[it.key()] = it.value();
}

QJsonDocument doc = QJsonDocument(QJsonObject::fromVariantMap(vmap));
headers += HttpHeader("Sub-Meta", doc.toJson(QJsonDocument::Compact));
}

{
QVariantMap vmap;
QHashIterator<QString, QString> it(context.publishMeta);
while(it.hasNext())
{
it.next();
vmap[it.key()] = it.value();
}

QJsonDocument doc = QJsonDocument(QJsonObject::fromVariantMap(vmap));
headers += HttpHeader("Pub-Meta", doc.toJson(QJsonDocument::Compact));
}

{
QHashIterator<QString, QString> it(context.prevIds);
while(it.hasNext())
{
it.next();
const QString &name = it.key();
const QString &prevId = it.value();

if(!prevId.isNull())
headers += HttpHeader("Grip-Last", name.toUtf8() + "; last-id=" + prevId.toUtf8());
}
}

req->start("POST", destUri, headers);

if(mode == Modify)
req->writeBody(content);

req->endBody();
}

void req_readyRead()
{
if(!haveResponseHeader)
{
haveResponseHeader = true;

switch(req->responseCode())
{
case 200:
case 204:
break;
default:
Result r;
r.errorMessage = "unexpected network request status";
finished(r);
return;
}
}

QByteArray body = req->readBody();

if(mode == Modify)
responseBody += body;

if(!req->isFinished())
return;

Result r;

if(req->responseHeaders().get("Action") == "drop")
{
// drop
r.sendAction = Filter::Drop;
}
else
{
// accept
r.sendAction = Filter::Send;

switch(req->responseCode())
{
case 204:
// as-is
r.content = origContent;
break;
default:
// replace content
r.content = responseBody;
break;
}
}

finished(r);
}

void req_error()
{
Result r;
r.errorMessage = "network request failed";
finished(r);
}
};

}

Filter::MessageFilter::~MessageFilter()
Expand Down Expand Up @@ -375,6 +565,10 @@ Filter::MessageFilter *Filter::createMessageFilter(const QString &name)
return new BuildIdFilter;
else if(name == "var-subst")
return new VarSubstFilter;
else if(name == "http-check")
return new HttpFilter(HttpFilter::Check);
else if(name == "http-modify")
return new HttpFilter(HttpFilter::Modify);
else
return 0;
}
Expand All @@ -386,7 +580,9 @@ QStringList Filter::names()
<< "skip-users"
<< "require-sub"
<< "build-id"
<< "var-subst");
<< "var-subst"
<< "http-check"
<< "http-modify");
}

Filter::Targets Filter::targets(const QString &name)
Expand All @@ -401,6 +597,10 @@ Filter::Targets Filter::targets(const QString &name)
return Filter::Targets(Filter::MessageContent | Filter::ResponseContent);
else if(name == "var-subst")
return Filter::MessageContent;
else if(name == "http-check")
return Filter::MessageDelivery;
else if(name == "http-modify")
return Filter::Targets(Filter::MessageDelivery | Filter::MessageContent);
else
return Filter::Targets(0);
}
Expand Down
15 changes: 15 additions & 0 deletions src/handler/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
#include <QStringList>
#include <QHash>
#include <QMetaType>
#include <QUrl>
#include <boost/signals2.hpp>

class ZhttpManager;

class Filter
{
public:
Expand All @@ -52,6 +55,18 @@ class Filter
QHash<QString, QString> prevIds;
QHash<QString, QString> subscriptionMeta;
QHash<QString, QString> publishMeta;

// for network access
ZhttpManager *zhttpOut;
QUrl currentUri;
QString route;
bool trusted;

Context() :
zhttpOut(0),
trusted(false)
{
}
};

class MessageFilter
Expand Down
5 changes: 5 additions & 0 deletions src/handler/handlerengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1813,6 +1813,10 @@ class HandlerEngine::Private : public QObject
Filter::Context fc;
fc.subscriptionMeta = s->meta;
fc.publishMeta = item.meta;
fc.zhttpOut = zhttpOut;
fc.currentUri = s->requestData.uri;
fc.route = s->route;
fc.trusted = s->targetTrusted;

FilterStack filters(fc, s->channelFilters[item.channel]);

Expand Down Expand Up @@ -2701,6 +2705,7 @@ class HandlerEngine::Private : public QObject

s->route = item.route;
s->statsRoute = item.separateStats ? item.route : QString();
s->targetTrusted = item.trusted;
s->channelPrefix = QString::fromUtf8(item.channelPrefix);
if(item.logLevel >= 0)
s->logLevel = item.logLevel;
Expand Down
10 changes: 9 additions & 1 deletion src/handler/httpsession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,10 @@ class HttpSession::Private : public QObject
fc.prevIds = prevIds;
fc.subscriptionMeta = instruct.meta;
fc.publishMeta = item.meta;
fc.zhttpOut = outZhttp;
fc.currentUri = currentUri;
fc.route = adata.route;
fc.trusted = adata.trusted;

FilterStack fs(fc, channels[item.channel].filters);

Expand Down Expand Up @@ -875,6 +879,10 @@ class HttpSession::Private : public QObject
fc.prevIds = prevIds;
fc.subscriptionMeta = instruct.meta;
fc.publishMeta = item.meta;
fc.zhttpOut = outZhttp;
fc.currentUri = currentUri;
fc.route = adata.route;
fc.trusted = adata.trusted;

FilterStack fs(fc, channels[item.channel].filters);

Expand Down Expand Up @@ -1229,7 +1237,7 @@ class HttpSession::Private : public QObject
passthroughData["prefer-internal"] = true;
}

// these fields are needed in case proxy routing is not used
// needed in case internal routing is not used
if(adata.trusted)
passthroughData["trusted"] = true;

Expand Down
4 changes: 3 additions & 1 deletion src/handler/wssession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
WsSession::WsSession(QObject *parent) :
QObject(parent),
nextReqId(0),
logLevel(LOG_LEVEL_DEBUG)
logLevel(LOG_LEVEL_DEBUG),
targetTrusted(false),
ttl(0)
{
expireTimer = new QTimer(this);
expireTimer->setSingleShot(true);
Expand Down
1 change: 1 addition & 0 deletions src/handler/wssession.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class WsSession : public QObject
HttpRequestData requestData;
QString route;
QString statsRoute;
bool targetTrusted;
QString sid;
QHash<QString, QString> meta;
QHash<QString, QStringList> channelFilters; // k=channel, v=list(filters)
Expand Down
2 changes: 1 addition & 1 deletion src/proxy/proxyutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ void manipulateRequestHeaders(const char *logprefix, void *object, HttpRequestDa

requestData->headers.removeAll("Grip-Feature");
requestData->headers += HttpHeader("Grip-Feature",
"status, session, link:next, link:gone, filter:skip-self, filter:skip-users, filter:require-sub, filter:build-id, filter:var-subst");
"status, session, link:next, link:gone, filter:skip-self, filter:skip-users, filter:require-sub, filter:build-id, filter:var-subst, filter:http-check, filter:http-modify");

if(!idata.sid.isEmpty())
{
Expand Down
Loading

0 comments on commit 5fc4533

Please sign in to comment.