deciding whether or not to adapt after examining headers.

Asked by Bryce Allen

I'm writing an ad-insertion adapter, but I only want to attempt the insert if the headers are appropriate, e.g. Content-Type contains text/html. It's not clear from the examples how to cleanly abort an adaptation when the headers don't match. I'm using lastHostCall()->useVirgin() in Xaction::start and returning immediately, but I'm getting HTML with lots of repeated copies of the original page.

I also only want to modify headers on requests (to remove Accept-Encoding). Is there a way to send back only the modified headers to the host and ask it to use the rest of the body as is?

I used the modifying example as a starting point. I've included my modifications below, any help would be greatly appreciated.

void Adapter::Xaction::start() {
    Must(hostx);
    if (hostx->virgin().body()) {
        receivingVb = opOn;
        hostx->vbMake(); // ask host to supply virgin body
    } else {
        receivingVb = opNever;
    }

    insertAd = false;

    libecap::FirstLine *firstLine = &(hostx->virgin().firstLine());
    libecap::RequestLine *requestLine = NULL;
    libecap::StatusLine *statusLine = NULL;

    requestLine = dynamic_cast<libecap::RequestLine*>(firstLine);
    if (requestLine == NULL) {
        statusLine = dynamic_cast<libecap::StatusLine*>(firstLine);
        if (statusLine == NULL || statusLine->statusCode() != 200) {
            sendingAb = opNever;
            lastHostCall()->useVirgin();
            return;
        }
    }

    if (statusLine != NULL) {
        // only process if a Content-Type header is present and contains
        // text/html, and no Content-Encoding header is present.
        static const libecap::Name headerContentEncoding("Content-Encoding");
        static const libecap::Name headerContentType("Content-Type");
        libecap::Header::Value contentType =
            hostx->virgin().header().value(headerContentType);
        if (contentType.size == 0
            || naiveStrMem(contentType.start, contentType.size,
                           "text/html") == NULL
            || hostx->virgin().header().hasAny(headerContentEncoding)) {
            sendingAb = opNever; // there is nothing to send
            lastHostCall()->useVirgin();
            return;
        }
    }

    libecap::shared_ptr<libecap::Message> adapted = hostx->virgin().clone();
    Must(adapted != 0);

    if (requestLine != NULL) {
        // do request stuff
        static const libecap::Name headerAcceptEncoding("Accept-Encoding");
        adapted->header().removeAny(headerAcceptEncoding);
        const libecap::Header::Value identityEncoding;
        adapted->header().add(headerAcceptEncoding, identityEncoding);
    } else {
        // response stuff

        // delete ContentLength header because we may change the length
        // unknown length may have performance implications for the host
        adapted->header().removeAny(libecap::headerContentLength);
        insertAd = true;
    }

    // add a custom header
    static const libecap::Name name("X-Ecap");
    const libecap::Header::Value value =
        libecap::Area::FromTempString(libecap::MyHost().uri());
    adapted->header().add(name, value);

    if (!adapted->body()) {
        sendingAb = opNever; // there is nothing to send
        lastHostCall()->useAdapted(adapted);
    } else {
        hostx->useAdapted(adapted);
    }
}

void Adapter::Xaction::noteVbContentAvailable()
{
    Must(receivingVb == opOn);

    const libecap::Area vb = hostx->vbContent(0, libecap::nsize); // get all vb
    std::string chunk = vb.toString(); // expensive, but simple
    if (insertAd)
        insertAd = !adaptContent(chunk);
    buffer += chunk; // buffer what we got

    if (sendingAb == opOn)
        hostx->noteAbContentAvailable();
}

bool Adapter::Xaction::adaptContent(std::string &chunk) const
{
    static const std::string victim = "<body>";
    static const std::string replacement = "<body><iframe src=\"http://server/ad.html\" scrolling=\"no\" frameborder=\"0\" width=\"100%\" height=\"100%\"></iframe><br>";

    std::string::size_type pos = 0;
    if ((pos = chunk.find(victim, 0)) != std::string::npos) {
        chunk.replace(pos, victim.length(), replacement);
        return true;
    }
    return false;
}

Question information

Language:
English Edit question
Status:
Solved
For:
eCAP Edit question
Assignee:
No assignee Edit question
Solved by:
Bryce Allen
Solved:
Last query:
Last reply:
Revision history for this message
Alex Rousskov (rousskov) said :
#1

I have not fully reviewed your code, but it looks like you call vbMake() before calling useVirgin() in start(). This may be fine in some cases, but it seems different from what you want to do.

There were a couple of bugs open against the sample adapters. Perhaps those bugs apply to your code as well? The problem description also sounds similar to some bugs in early Squid eCAP implementations. Please make sure you use a recent 3.1 version.

There is currently no way to adapt the message header only but I think we should add such a feature. Meanwhile, you can just send back the virgin body as soon as it comes in. I believe there is a sample showing how to do that.

Good luck,

Alex.

Revision history for this message
Bryce Allen (oss10) said :
#2

Thanks Alex. I integrated the changes from the patches on bugs 320530, 338137, and 362069 and my adapter is working now. I also moved the vbMake() down below the header checks and that is working also, I had left it there because I wasn't sure what was required to access the virgin header.

I would recommend either updating or removing the tarballs on the homepage, because of the outstanding major bugs. Not all users are going to check the Bug list (even though we should), and requiring them to get the code from SVN is less likely to alienate potential users and contributors than having broken tarballs.

Thanks again for your help!

-Bryce

Revision history for this message
Steve (steve-inteliport) said :
#3

Hi Bryce,

could you please share the ad-insertion adapter that you created I'm also trying to create one and having some issues with my code.

Thanks in advance