Wednesday, March 25, 2020

CDN | Clearing Cloudflare cache

In order to clear Cloudflare cache automatically via code, follow below steps:

1. Develop Custom TransportHandler

Develop a custom Transport handler to send the test and purge requests to Cloudflare API server. The handler sets up basic authentication with the user/pass from the replication agent's transport config and sends a GET request as a test and POST as purge request. A valid test response is 200 while a valid purge response is 201.

Sample code is here:

package com.vivek.sample.handler.impl;
import java.io.IOException;
import java.util.HashMap;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.commons.Externalizer;
import com.day.cq.replication.AgentConfig;
import com.day.cq.replication.ReplicationActionType;
import com.day.cq.replication.ReplicationResult;
import com.day.cq.replication.ReplicationTransaction;
import com.day.cq.replication.TransportContext;
import com.day.cq.replication.TransportHandler;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
* Transport handler to send test and purge requests to Cloudflare and handle
* responses. The handler sets up basic authentication with the user/pass from
* the replication agent's transport config and sends a GET request as a test
* and POST as purge request. A valid test response is 200 while a valid purge
* response is 201.
*
* The Flush agent must be configured with following 3 properties:
*
* 1. The transport handler is triggered by setting your replication agent's
* transport URL's protocol to "cloudflare://". E.g. -
* cloudflare://api.cloudflare.com/client/v4/zones/{zone-id}/purge_cache
*
* 2. User: The X-Auth-Email value of cloudflare account
*
* 3. Password: X-Auth-Key value of cloudflare account
*
* The transport handler builds the POST request body in accordance with
* Cloudflare's REST APIs
* {@link https://api.cloudflare.com/#zone-purge-files-by-url/} using the
* replication agent properties.
*/
@Component(service = TransportHandler.class, name = "Cloudflare-Purge-Agent", immediate = true)
public class CloudflareTransportHandler implements TransportHandler {
/**
* externalizer
*/
@Reference
private Externalizer externalizer;
/**
* resolverFactory
*/
@Reference
private ResourceResolverFactory resolverFactory;
/**
* Protocol for replication agent transport URI that triggers this transport
* handler.
*/
private static final String CF_PROTOCOL = "cloudflare://";
/**
* Key parameter name
*/
private static final String CF_PARAM_KEY = "X-Auth-Key";
/**
* email parameter name
*/
private static final String CF_PARAM_EMAIL = "X-Auth-Email";
/**
* files parameter name
*/
private static final String CF_PARAM_FILES = "files";
/**
* CF test endpoint
*/
private static final String CF_TEST_ENDPOINT = "https://%s/client/v4/zones";
/**
* Timeout for http requests
*/
private static final int HTTP_TIMEOUT = 15;
/**
* Logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(CloudflareTransportHandler.class);
/**
* {@inheritDoc}
*/
@Override
public final boolean canHandle(final AgentConfig config) {
final String transportURI = config.getTransportURI();
if (transportURI != null) {
return transportURI.toLowerCase().startsWith(CF_PROTOCOL);
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public final ReplicationResult deliver(final TransportContext ctx, final ReplicationTransaction tx) {
final ReplicationActionType replicationType = tx.getAction().getType();
if (replicationType == ReplicationActionType.TEST) {
return doTest(ctx, tx);
} else if (replicationType == ReplicationActionType.ACTIVATE
|| replicationType == ReplicationActionType.DEACTIVATE) {
return doActivate(ctx, tx);
}
return ReplicationResult.OK;
}
/**
* Send test request to Cloudflare via a GET request.
*
* Cloudflare will respond with a 200 HTTP status code if the request was
* successfully submitted. The response will have information about the queue
* length, but we're simply interested in the fact that the request was
* authenticated.
*
* @param ctx Transport Context
* @param tx Replication Transaction
* @return ReplicationResult OK if 200 response from Cloudflare
*/
private ReplicationResult doTest(final TransportContext ctx, final ReplicationTransaction tx) {
String uri = ctx.getConfig().getTransportURI().replace(CF_PROTOCOL, "");
String domain = uri.substring(0, uri.indexOf("/"));
final HttpGet request = new HttpGet(String.format(CF_TEST_ENDPOINT, domain));
return getResult(sendRequest(request, ctx, tx), tx);
}
/**
* Send purge request to Cloudflare via a POST request
*
* Cloudflare will respond with a 201 HTTP status code if the purge request was
* successfully submitted.
*
* @param ctx - Transport Context
* @param tx - Replication Transaction
* @return ReplicationResult - OK if 201 response from Cloudflare
*/
private ReplicationResult doActivate(final TransportContext ctx, final ReplicationTransaction tx) {
final String cfEndPoint = ctx.getConfig().getTransportURI().replace(CF_PROTOCOL, "https://");
final HttpPost request = new HttpPost(cfEndPoint);
createPostBody(request, tx);
return getResult(sendRequest(request, ctx, tx), tx);
}
/**
* Get Replication Result
*
* @param response - HttpResponse object
* @param tx - ReplicationTransaction object
* @return ReplicationResult - result
*/
private ReplicationResult getResult(final HttpResponse response, final ReplicationTransaction tx) {
if (response != null) {
final int statusCode = response.getStatusLine().getStatusCode();
tx.getLog().info(response.toString());
tx.getLog().info("---------------------------------------");
if (statusCode == HttpStatus.SC_OK) {
return ReplicationResult.OK;
}
}
return new ReplicationResult(false, 0, "Replication failed");
}
/**
* Build preemptive basic authentication headers and send request.
*
* @param <T> Type
* @param request - The request to send to Cloudflare
* @param ctx - The TransportContext containing the username and password
* @param tx - Replication Transaction
* @return HttpResponse - The HTTP response from Cloudflare
*/
private <T extends HttpRequestBase> HttpResponse sendRequest(final T request, final TransportContext ctx,
final ReplicationTransaction tx) {
request.setHeader(CF_PARAM_KEY, ctx.getConfig().getTransportPassword());
request.setHeader(CF_PARAM_EMAIL, ctx.getConfig().getTransportUser());
request.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
RequestConfig config = RequestConfig.custom().setConnectTimeout(HTTP_TIMEOUT * 1000)
.setConnectionRequestTimeout(HTTP_TIMEOUT * 1000)
.setSocketTimeout(HTTP_TIMEOUT * 1000).build();
HttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
HttpResponse response = null;
try {
response = client.execute(request);
} catch (IOException e) {
tx.getLog().error("Could not send replication request- " + e.getMessage());
}
return response;
}
/**
* Build the Cloudflare purge request body based on the replication agent
* settings and append it to the POST request.
*
* @param request The HTTP POST request to append the request body
* @param tx ReplicationTransaction
*/
private void createPostBody(final HttpPost request, final ReplicationTransaction tx) {
JsonObject json = new JsonObject();
JsonArray purgeObjects = new JsonArray();
for (String path : tx.getAction().getPaths()) {
if (StringUtils.isNotBlank(path) && path.startsWith("/content/")) {
purgeObjects.add(externalizer.externalLink(getServiceResourceResolver(resolverFactory, "read-service"),
Externalizer.PUBLISH, path));
}
}
if (purgeObjects.size() > 0) {
json.add(CF_PARAM_FILES, purgeObjects);
final StringEntity entity = new StringEntity(json.toString(), CharEncoding.ISO_8859_1);
tx.getLog().info("Clearing cache for paths param: " + json);
request.setEntity(entity);
}
}
/**
* Get Resolver
*
* @param resolverFactory - resolver factory
* @param subservice - sub-service
* @return ResourceResolver resolver
*/
private static ResourceResolver getServiceResourceResolver(final ResourceResolverFactory resolverFactory,
final String subservice) {
HashMap<String, Object> param = new HashMap<>();
param.put(ResourceResolverFactory.SUBSERVICE, subservice);
try {
return resolverFactory.getServiceResourceResolver(param);
} catch (LoginException e) {
LOGGER.error("Login Excpetion in getting service resource resolver. Error: {}", e.getMessage(), e);
}
return null;
}
}

2. Create custom dispatcher flush agent

The transport handler builds the POST request body in accordance with Cloudflare's REST APIs using the replication agent properties.

A. Create New Replication Agent at-

      http://localhost:4503/miscadmin#/etc/replication/agents.publish

B. Agent Configuration:

     The Flush agent must be configured with following 3 properties:
     1. The transport handler is triggered by setting agent's transport URL's protocol to "cloudflare://".
        E.g. - cloudflare://api.cloudflare.com/client/v4/zones/{zone-id}/purge_cache
     2. User: The X-Auth-Email value of cloudflare account
     3. Password: X-Auth-Key value of cloudflare account

C. Agent User Id in AEM:


Create a custom service user to control permission what should be allowed to flush CDN cache: cloudflare-flush

Reference: https://api.cloudflare.com/#zone-purge-files-by-url/

No comments:

Post a Comment

CDN | Clearing Cloudflare cache

In order to clear Cloudflare cache automatically via code, follow below steps: 1. Develop Custom TransportHandler Develop a custom Trans...