Skip to content

Commit 5a9fc08

Browse files
authored
add http-check and http-modify filters (#48089)
1 parent f3db861 commit 5a9fc08

File tree

3 files changed

+438
-8
lines changed

3 files changed

+438
-8
lines changed

src/handler/filter.cpp

Lines changed: 211 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323

2424
#include "filter.h"
2525

26+
#include <QJsonDocument>
27+
#include <QJsonObject>
2628
#include "log.h"
2729
#include "format.h"
2830
#include "idformat.h"
31+
#include "zhttpmanager.h"
32+
#include "zhttprequest.h"
2933

3034
namespace {
3135

@@ -304,6 +308,202 @@ class VarSubstFilter : public Filter, public Filter::MessageFilter
304308
}
305309
};
306310

311+
class HttpFilter : public Filter::MessageFilter
312+
{
313+
public:
314+
enum Mode
315+
{
316+
Check,
317+
Modify
318+
};
319+
320+
Mode mode;
321+
std::unique_ptr<ZhttpRequest> req;
322+
boost::signals2::scoped_connection readyReadConnection;
323+
boost::signals2::scoped_connection errorConnection;
324+
QByteArray origContent;
325+
bool haveResponseHeader;
326+
QByteArray responseBody;
327+
328+
HttpFilter(Mode _mode) :
329+
mode(_mode),
330+
haveResponseHeader(false)
331+
{
332+
}
333+
334+
virtual void start(const Filter::Context &context, const QByteArray &content)
335+
{
336+
QUrl url = QUrl(context.subscriptionMeta.value("url"), QUrl::StrictMode);
337+
if(!url.isValid())
338+
{
339+
Result r;
340+
r.errorMessage = "invalid or missing url value";
341+
finished(r);
342+
return;
343+
}
344+
345+
QUrl currentUri = context.currentUri;
346+
if(currentUri.scheme() == "wss")
347+
currentUri.setScheme("https");
348+
else if(currentUri.scheme() == "ws")
349+
currentUri.setScheme("http");
350+
351+
QUrl destUri = currentUri.resolved(url);
352+
353+
origContent = content;
354+
355+
req.reset(context.zhttpOut->createRequest());
356+
readyReadConnection = req->readyRead.connect(boost::bind(&HttpFilter::req_readyRead, this));
357+
errorConnection = req->error.connect(boost::bind(&HttpFilter::req_error, this));
358+
359+
int currentPort = currentUri.port(currentUri.scheme() == "https" ? 443 : 80);
360+
int destPort = destUri.port(destUri.scheme() == "https" ? 443 : 80);
361+
362+
QVariantHash passthroughData;
363+
364+
passthroughData["route"] = context.route.toUtf8();
365+
366+
// if dest link points to the same service as the current request,
367+
// then we can assume the network would send the request back to
368+
// us, so we can handle it internally. if the link points to a
369+
// different service, then we can't make this assumption and need
370+
// to make the request over the network. note that such a request
371+
// could still end up looping back to us
372+
if(destUri.scheme() == currentUri.scheme() && destUri.host() == currentUri.host() && destPort == currentPort)
373+
{
374+
// tell the proxy that we prefer the request to be handled
375+
// internally, using the same route
376+
passthroughData["prefer-internal"] = true;
377+
}
378+
379+
// needed in case internal routing is not used
380+
if(context.trusted)
381+
passthroughData["trusted"] = true;
382+
383+
req->setPassthroughData(passthroughData);
384+
385+
HttpHeaders headers;
386+
387+
{
388+
QVariantMap vmap;
389+
QHashIterator<QString, QString> it(context.subscriptionMeta);
390+
while(it.hasNext())
391+
{
392+
it.next();
393+
vmap[it.key()] = it.value();
394+
}
395+
396+
QJsonDocument doc = QJsonDocument(QJsonObject::fromVariantMap(vmap));
397+
headers += HttpHeader("Sub-Meta", doc.toJson(QJsonDocument::Compact));
398+
}
399+
400+
{
401+
QVariantMap vmap;
402+
QHashIterator<QString, QString> it(context.publishMeta);
403+
while(it.hasNext())
404+
{
405+
it.next();
406+
vmap[it.key()] = it.value();
407+
}
408+
409+
QJsonDocument doc = QJsonDocument(QJsonObject::fromVariantMap(vmap));
410+
headers += HttpHeader("Pub-Meta", doc.toJson(QJsonDocument::Compact));
411+
}
412+
413+
{
414+
QHashIterator<QString, QString> it(context.prevIds);
415+
while(it.hasNext())
416+
{
417+
it.next();
418+
const QString &name = it.key();
419+
const QString &prevId = it.value();
420+
421+
if(!prevId.isNull())
422+
headers += HttpHeader("Grip-Last", name.toUtf8() + "; last-id=" + prevId.toUtf8());
423+
}
424+
}
425+
426+
req->start("POST", destUri, headers);
427+
428+
if(mode == Modify)
429+
req->writeBody(content);
430+
431+
req->endBody();
432+
}
433+
434+
void req_readyRead()
435+
{
436+
if(!haveResponseHeader)
437+
{
438+
haveResponseHeader = true;
439+
440+
int code = req->responseCode();
441+
switch(code)
442+
{
443+
case 200:
444+
case 204:
445+
break;
446+
default:
447+
Result r;
448+
r.errorMessage = QString("unexpected network request status: code=%1").arg(code);
449+
finished(r);
450+
return;
451+
}
452+
}
453+
454+
QByteArray body = req->readBody();
455+
456+
if(mode == Modify)
457+
responseBody += body;
458+
459+
if(!req->isFinished())
460+
return;
461+
462+
Result r;
463+
464+
if(req->responseHeaders().get("Action") == "drop")
465+
{
466+
// drop
467+
r.sendAction = Filter::Drop;
468+
}
469+
else
470+
{
471+
// accept
472+
r.sendAction = Filter::Send;
473+
474+
switch(mode)
475+
{
476+
case Check:
477+
// as-is
478+
r.content = origContent;
479+
break;
480+
case Modify:
481+
switch(req->responseCode())
482+
{
483+
case 204:
484+
// as-is
485+
r.content = origContent;
486+
break;
487+
default:
488+
// replace content
489+
r.content = responseBody;
490+
break;
491+
}
492+
break;
493+
}
494+
}
495+
496+
finished(r);
497+
}
498+
499+
void req_error()
500+
{
501+
Result r;
502+
r.errorMessage = "network request failed";
503+
finished(r);
504+
}
505+
};
506+
307507
}
308508

309509
Filter::MessageFilter::~MessageFilter() = default;
@@ -371,6 +571,10 @@ Filter::MessageFilter *Filter::createMessageFilter(const QString &name)
371571
return new BuildIdFilter;
372572
else if(name == "var-subst")
373573
return new VarSubstFilter;
574+
else if(name == "http-check")
575+
return new HttpFilter(HttpFilter::Check);
576+
else if(name == "http-modify")
577+
return new HttpFilter(HttpFilter::Modify);
374578
else
375579
return 0;
376580
}
@@ -382,7 +586,9 @@ QStringList Filter::names()
382586
<< "skip-users"
383587
<< "require-sub"
384588
<< "build-id"
385-
<< "var-subst");
589+
<< "var-subst"
590+
<< "http-check"
591+
<< "http-modify");
386592
}
387593

388594
Filter::Targets Filter::targets(const QString &name)
@@ -397,6 +603,10 @@ Filter::Targets Filter::targets(const QString &name)
397603
return Filter::Targets(Filter::MessageContent | Filter::ResponseContent);
398604
else if(name == "var-subst")
399605
return Filter::MessageContent;
606+
else if(name == "http-check")
607+
return Filter::MessageDelivery;
608+
else if(name == "http-modify")
609+
return Filter::Targets(Filter::MessageDelivery | Filter::MessageContent);
400610
else
401611
return Filter::Targets(0);
402612
}

0 commit comments

Comments
 (0)