March 29, 2020
Estimated Post Reading Time ~

How to Implement 301 and 302 Redirect in AEM

A website redirect will take one website URL and point it to another. When anyone types in or click on that original URL they’ll be taken to the new page or website.

There are a few different types of redirects.

301 Redirect: A 301 redirect is a permanent redirect from one URL to another. 301 redirects send site visitors and search engines to a different URL than the one they originally typed into their browser or selected from a search engine results page.

The big reasons marketers might set up a 301 redirect are:

To rebrand or rename a website with a different URL.
To direct traffic to a website from other URLs owned by the same organization.

Note: In the case of 301 redirect browser cache the mapping of new URL with the old URL.

302 Redirect:A 302 status code means Found, or more commonly referred to as “temporarily moved.” This redirect doesn’t carry or pass the link value to the new location. What it does do is get the user to an appropriate location for you so that you aren’t showing them a broken link, a 404 page not found, or an error page.

Note: In the case of 302 redirect browser does not maintain any mapping or cache. So, the server receives hit for both the URLs.

In AEM, OOTB redirect URLs do 302 Redirect. Redirect URL will only work in disabled and publish mode but not in edit mode.


Fig-1 Redirect Widget in Page Properties Dialog

So How to do 301/302 Redirect in AEM? 
Add a custom widget as drop down to select 301 and 301 redirect and value must be 301 and 302 only.


Fig-2 Redirect Type Option in Page Properties

Let’s start with AEM 6.2/AEM 6.3 with 301/302 Redirect

The logic of redirection is written in /libs/foundation/components/page/page.jsp file so need to seperate the redirection logic in one another jsp file may be “redirect.jsp” but logic in this jsp only do 302 redirect so let’s add logic for 301 redirect.and include that jsp in header.html. 

The implementation of 301 and 302 Redirection is exactly same in AEM 6.2 and AEM 6.3

redirect.jsp having the logic for 301 and 302 redirection:

<%@page session="false"
import="com.day.cq.wcm.api.WCMMode,
com.day.cq.wcm.foundation.ELEvaluator" %><%
%><%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0" %><%
%><cq:defineObjects/><%
String location = properties.get("redirectTarget", "");
String type = properties.get("redirectType", "");
// resolve variables in path
location = ELEvaluator.evaluate(location, slingRequest, pageContext);
boolean wcmModeIsDisabled = WCMMode.fromRequest(request) == WCMMode.DISABLED;
boolean wcmModeIsPreview = WCMMode.fromRequest(request) == WCMMode.PREVIEW;
if ( (location.length() > 0) && ((wcmModeIsDisabled)) ) {
// check for recursion
if (currentPage != null && !location.equals(currentPage.getPath()) && location.length() > 0) {
// check for absolute path
final int protocolIndex = location.indexOf(":/");
final int queryIndex = location.indexOf('?');
String redirectPath;

if ( protocolIndex > -1 && (queryIndex == -1 || queryIndex > protocolIndex) ) {
redirectPath = location;
} else {
redirectPath = slingRequest.getResourceResolver().map(request, location) + ".html";
}

// include wcmmode=disabled to redirected url if original request also had that parameter
if (wcmModeIsDisabled) {
redirectPath += ((redirectPath.indexOf('?') == -1) ? '?' : '&') + "wcmmode=disabled";
}
if (type.equals("301")) {
response.setStatus(301);
response.setHeader("Location",redirectPath);
} else {
response.sendRedirect(redirectPath);
}

} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
return;
}
%>


AEM 6.4 with 301/302 Redirect:
In AEM 6.4, AEM has provided a feature of redirecting a page by default.It means no need to write any redirect.jsp for 302 Redirection.

So how 302 Redirect is working in AEM 6.4?
My observation: I created a page component without any inheritance and created a
page configured a redirect path in it.

While rendering a page this jsp "/libs/cq/Page/Page.jsp" gets called. This jsp
includes proxy.jsp and that proxy.jsp is calling a servlet com.day.cq.wcm.foundation.impl.PageRedirectServlet.java with selector "redirect".


Fig-3 : Call PageRedirectServlet from proxy.jsp

com.day.cq.wcm.foundation.impl.PageRedirectServlet.java class exists in the bundle shown below.


Fig-4: Bundle that has the class PageRedirectServlet.java

Now the question come how to configure 301 in AEM 6.4??
To achieve 301 redirect,

Overlay /libs/cq/Page hierarchy and make a servlet with a different selector and make an entry of that selector in proxy.jsp.So that always your servlet will get called (in which the logic for 301 and 302 redirection is written ) in place of OOTB servlet.
Make your own servlet class with the same selector(redirect) with 301 redirect logic in it and make the service ranking of this servlet higher.OOTB Servlet is not using any service ranking, so you can create servlet using service ranking and then always your servlet will get called.


Fig-5: Servlet Resolver Console that shows the scripts running for redirection as per preference
Custom PageRedirectServlet

package com.aem.project.core.servlets;

import com.day.cq.wcm.api.WCMMode;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestDispatcherOptions;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(service = Servlet.class,
property = {
Constants.SERVICE_DESCRIPTION + "= Redirection Servlet",
"sling.servlet.extensions=" + "html",
"sling.servlet.selectors=" + "redirect",
"sling.servlet.resourceTypes=" + "cq/Page",
Constants.SERVICE_RANKING + "=700"
})
public class PageRedirectServlet
extends SlingSafeMethodsServlet {

private Set<String> excludedResourceTypes;
private static Logger LOG = LoggerFactory.getLogger(PageRedirectServlet.class);
private static final String WCM_MODE_PARAM = "wcmmode";

@Activate
protected void activate(Map<String, Object> properties) {
String[] excludedResourceTypesArray = PropertiesUtil.toStringArray(properties.get("excluded.resource.types"));
this.excludedResourceTypes = new HashSet();
if (!ArrayUtils.isEmpty(excludedResourceTypesArray)) {
Collections.addAll(this.excludedResourceTypes, excludedResourceTypesArray);
}
}

protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
Resource resource = request.getResource();
Resource contentResource = resource.getChild("jcr:content");
if (contentResource != null) {
String redirectTarget = getRedirectTarget(contentResource);
String redirectType = getRedirectType(contentResource);
if ((isRedirectRequest(request, redirectTarget)) && (!isExcludedResourceType(contentResource))) {
if (!isExternalRedirect(redirectTarget)) {
redirectTarget = resource.getResourceResolver().map(request, redirectTarget) + ".html";
}
redirectTarget = appendWcmModeQueryParameter(request, redirectTarget);
LOG.debug("Redirecting page {} to target {}", resource.getPath(), redirectTarget);
if (redirectType.equals("301")) {
response.setStatus(301);
response.setHeader("Location", redirectTarget);
} else
response.sendRedirect(redirectTarget);
return;
}
}
RequestDispatcherOptions requestDispatcherOptions = new RequestDispatcherOptions();
String selectorString = request.getRequestPathInfo().getSelectorString();
selectorString = StringUtils.replace(selectorString, "redirect", "");
requestDispatcherOptions.setReplaceSelectors(selectorString);

RequestDispatcher requestDispatcher = request.getRequestDispatcher(contentResource, requestDispatcherOptions);
if (requestDispatcher != null) {
requestDispatcher.include(request, response);
}
}

private boolean isExcludedResourceType(Resource contentResource) {
for (String excludedResourceType : this.excludedResourceTypes) {
if (contentResource.isResourceType(excludedResourceType)) {
return true;
}
}
return false;
}

private String appendWcmModeQueryParameter(SlingHttpServletRequest request, String redirectTarget) {
if (isModeDisabledChangeRequest(request)) {
redirectTarget = redirectTarget + (redirectTarget.contains("?") ? "&" : "?") + "wcmmode" + "=disabled";
}
return redirectTarget;
}

private boolean isModeDisabledChangeRequest(SlingHttpServletRequest request) {
boolean isModeChangeRequest = false;
String modeChange = request.getParameter("wcmmode");
if (StringUtils.equalsIgnoreCase(modeChange, WCMMode.DISABLED.name())) {
isModeChangeRequest = true;
}
return isModeChangeRequest;
}

private boolean isExternalRedirect(String redirectTarget) {
boolean externalRedirect = false;
try {
URL url = new URL(redirectTarget);
String protocol = url.getProtocol();
if (StringUtils.isNotBlank(protocol)) {
externalRedirect = true;
}
} catch (MalformedURLException e) {
return false;
}
return externalRedirect;
}

private String getRedirectTarget(Resource resource) {
ValueMap valueMap = resource.adaptTo(ValueMap.class);
String redirectTarget = "";
if (valueMap != null) {
redirectTarget = (String) valueMap.get("cq:redirectTarget", "");
}
return redirectTarget;
}

private String getRedirectType(Resource resource) {
ValueMap valueMap = resource.adaptTo(ValueMap.class);
String redirectTarget = "";
if (valueMap != null) {
redirectTarget = valueMap.get("redirectType", "");
}
return redirectTarget;
}

private boolean isRedirectRequest(SlingHttpServletRequest request, String redirectTarget) {
WCMMode wcmMode = WCMMode.fromRequest(request);
return (StringUtils.isNotEmpty(redirectTarget)) && (wcmMode.equals(WCMMode.DISABLED));
}
}


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.