Skip to content

Commit

Permalink
#21282 Identify which node a response comes from
Browse files Browse the repository at this point in the history
* #21282 adding the metadata interceptor

* #21282 adding unit test

* #21282 feedback and unit test

* #21282 adding more doc

* #21282 unit test feedback

* #21282 adding the ability to show the node id by query string

* #21282 feedback done

* #21282 fixing unit test

* #21282 fixing a test again
  • Loading branch information
jdotcms authored Jan 28, 2022
1 parent c509847 commit 0125923
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 2 deletions.
2 changes: 2 additions & 0 deletions dotCMS/src/integration-test/java/com/dotcms/MainSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.dotcms.enterprise.publishing.staticpublishing.LanguageFolderTest;
import com.dotcms.enterprise.publishing.staticpublishing.StaticPublisherIntegrationTest;
import com.dotcms.enterprise.rules.RulesAPIImplIntegrationTest;
import com.dotcms.filters.interceptor.meta.MetaWebInterceptorTest;
import com.dotcms.graphql.DotGraphQLHttpServletTest;
import com.dotcms.integritycheckers.HostIntegrityCheckerTest;
import com.dotcms.junit.MainBaseSuite;
Expand Down Expand Up @@ -495,6 +496,7 @@
Task211012AddCompanyDefaultLanguageTest.class,
StaticPublisherIntegrationTest.class,
HostIntegrityCheckerTest.class,
MetaWebInterceptorTest.class,
BrowserUtilTest.class,
Task211101AddContentletAsJsonColumnTest.class,
ContentletJsonAPITest.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.dotcms.filters.interceptor.meta;

import com.dotcms.enterprise.ClusterUtilProxy;
import com.dotcms.mock.response.MockHeaderResponse;
import com.dotcms.util.IntegrationTestInitService;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.StringUtils;
import com.liferay.util.StringPool;
import io.vavr.control.Try;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

import static org.mockito.Mockito.mock;

/**
* Unit test for {@link ResponseMetaDataWebInterceptor}
* @author jsanca
*/
public class MetaWebInterceptorTest {

private final HttpServletRequest request = mock(HttpServletRequest.class);
private final HttpServletResponse response = mock(HttpServletResponse.class);


@BeforeClass
public static void prepare() throws Exception {
//Setting web app environment
IntegrationTestInitService.getInstance().init();
}

/**
* Method to test: {@link ResponseMetaDataWebInterceptor#intercept(HttpServletRequest, HttpServletResponse)}
* Given Scenario: calling it adds the header x-dot-server
* ExpectedResult: the header is added
* @throws IOException
*/
@Test
public void check_header_x_dot_server() {

final ResponseMetaDataWebInterceptor metaWebInterceptor = new ResponseMetaDataWebInterceptor();
final MockHeaderResponse mockHeaderResponse = new MockHeaderResponse(response);
metaWebInterceptor.intercept(request, mockHeaderResponse);

final String header = mockHeaderResponse.getHeader(ResponseMetaDataWebInterceptor.X_DOT_SERVER_HEADER);
Assert.assertNotNull(header);

final String tokenHeader = "unknown" + StringPool.PIPE + StringUtils.shortify(APILocator.getServerAPI().readServerId(), 10);

Assert.assertEquals(tokenHeader, header);
}


/**
* Method to test: {@link ResponseMetaDataWebInterceptor#intercept(HttpServletRequest, HttpServletResponse)}
* Given Scenario: calling it does not adds the header x-dot-server
* ExpectedResult: the header is not added
* @throws IOException
*/
@Test
public void check_header_x_dot_server_config_disable() {

try {
Config.setProperty(ResponseMetaDataWebInterceptor.RESPONSE_HEADER_ADD_NODE_ID, false);
final ResponseMetaDataWebInterceptor metaWebInterceptor = new ResponseMetaDataWebInterceptor();
final MockHeaderResponse mockHeaderResponse = new MockHeaderResponse(response);
metaWebInterceptor.intercept(request, mockHeaderResponse);

final String header = mockHeaderResponse.getHeader(ResponseMetaDataWebInterceptor.X_DOT_SERVER_HEADER);
Assert.assertNull(header);
} finally {

Config.setProperty(ResponseMetaDataWebInterceptor.RESPONSE_HEADER_ADD_NODE_ID, true);
}
}


/**
* Method to test: {@link ResponseMetaDataWebInterceptor#intercept(HttpServletRequest, HttpServletResponse)}
* Given Scenario: calling it adds the header x-dot-server, but do not includes the node name (unknown instead)
* ExpectedResult: the header is added without node name
* @throws IOException
*/
@Test
public void check_header_x_dot_server_config_disable_node_name() {

try {
Config.setProperty(ResponseMetaDataWebInterceptor.RESPONSE_HEADER_ADD_NODE_ID_INCLUDE_NODE_NAME, false);
final ResponseMetaDataWebInterceptor metaWebInterceptor = new ResponseMetaDataWebInterceptor();
final MockHeaderResponse mockHeaderResponse = new MockHeaderResponse(response);
metaWebInterceptor.intercept(request, mockHeaderResponse);

final String header = mockHeaderResponse.getHeader(ResponseMetaDataWebInterceptor.X_DOT_SERVER_HEADER);
Assert.assertNotNull(header);
Assert.assertTrue(header.startsWith("unknown|"));
} finally {

Config.setProperty(ResponseMetaDataWebInterceptor.RESPONSE_HEADER_ADD_NODE_ID, true);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.dotcms.filters.interceptor.meta;

import com.dotcms.enterprise.ClusterUtilProxy;
import com.dotcms.filters.interceptor.Result;
import com.dotcms.filters.interceptor.WebInterceptor;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.StringUtils;
import com.liferay.util.StringPool;
import io.vavr.Lazy;
import io.vavr.control.Try;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;

/**
* Interceptor that adds meta info to the dotcms response
*
* includes:
* - x-dot-server header, contains the node name + server id (this helps to identified on a cluster context which node has committed the response)
*
* @author jsanca
*/
public class ResponseMetaDataWebInterceptor implements WebInterceptor {

public static final String RESPONSE_HEADER_ADD_NODE_ID = "RESPONSE_HEADER_ADD_NODE_ID";
public static final String RESPONSE_HEADER_ADD_NODE_ID_INCLUDE_NODE_NAME = "RESPONSE_HEADER_ADD_NODE_ID_INCLUDE_NODE_NAME";
public static final String X_DOT_SERVER_HEADER = "x-dot-server";

private static final String FRIENDLY_NAME = "friendlyName";
private static final String UNKNOWN = "unknown";
private static final String NODEID_PARAM = "nodeid";
private static final String TRUE_VALUE = "true";

private final Lazy<Boolean> responseHeaderAddNodeId = Lazy.of(()-> Config.getBooleanProperty(RESPONSE_HEADER_ADD_NODE_ID, true));
private final Lazy<String> serverId = Lazy.of(()-> StringUtils.shortify(APILocator.getServerAPI().readServerId(), 10));
private final Lazy<String> nodeName = Lazy.of(this::getNodeName);

private final String getNodeName () {

if (Config.getBooleanProperty(RESPONSE_HEADER_ADD_NODE_ID_INCLUDE_NODE_NAME, true)) {

final Map<String, Serializable> nodeInfoMap = Try.of(() -> ClusterUtilProxy.getNodeInfo()).getOrElse(Collections.emptyMap());
return nodeInfoMap.getOrDefault(FRIENDLY_NAME, UNKNOWN).toString();
}

return UNKNOWN;
}

@Override
public Result intercept(final HttpServletRequest request, final HttpServletResponse response) {

if (responseHeaderAddNodeId.get() ||
TRUE_VALUE.equals(request.getParameter(NODEID_PARAM))) {

response.addHeader(X_DOT_SERVER_HEADER,
nodeName.get()
+ StringPool.PIPE
+ serverId.get()
);
}

return Result.NEXT;
}

} // E:O:F:MetaWebInterceptor.
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public CircuitBreakerUrl build() {
default:
request = new HttpGet(proxyUrl);
break;
}
}

return new CircuitBreakerUrl(this.proxyUrl, this.timeout, this.circuitBreaker, request, this.params, this.headers, this.verbose, this.rawData);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.dotcms.ema.EMAWebInterceptor;
import com.dotcms.filters.interceptor.AbstractWebInterceptorSupportFilter;
import com.dotcms.filters.interceptor.WebInterceptorDelegate;
import com.dotcms.filters.interceptor.meta.ResponseMetaDataWebInterceptor;
import com.dotcms.graphql.GraphqlCacheWebInterceptor;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
Expand All @@ -28,6 +29,7 @@ private void addInterceptors(final FilterConfig config) {

delegate.add(new EMAWebInterceptor());
delegate.add(new GraphqlCacheWebInterceptor());
delegate.add(new ResponseMetaDataWebInterceptor());
} // addInterceptors.

} // E:O:F:InterceptorFilter.
} // E:O:F:InterceptorFilter.
2 changes: 2 additions & 0 deletions dotCMS/src/main/java/com/liferay/util/StringPool.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,6 @@ public class StringPool {

public static final String PLUS = "+";

public static final String PIPE = "|";

}

0 comments on commit 0125923

Please sign in to comment.