Skip to content

Commit

Permalink
feat(gateway-engine): Allow policies to mutate certain connector prop…
Browse files Browse the repository at this point in the history
…erties

Adds IConnectorConfig, which we can expand to allow twiddling of various
connector configuration aspects by policies.

Signed-off-by: Marc Savy <marc@rhymewithgravy.com>
  • Loading branch information
msavy committed Jun 9, 2018
1 parent 02f4921 commit ba38f09
Show file tree
Hide file tree
Showing 24 changed files with 535 additions and 101 deletions.
@@ -0,0 +1,70 @@
/*
* Copyright 2018 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.apiman.gateway.engine;

import java.util.Set;

/**
* Allows policies to mutate certain HTTP connector attributes.
*
* @author Marc Savy {@literal <marc@rhymewithgravy.com>}
*/
public interface IConnectorConfig {
/**
* Suppress the named request header
*
* @param headerName the header name
*/
void suppressRequestHeader(String headerName);

/**
* Suppress the named response header
*
* @param headerName the header name
*/
void suppressResponseHeader(String headerName);

/**
* Permit a request header that may be suppressed.
*
* @param headerName the header name
*/
void permitRequestHeader(String headerName);

/**
* Permit a response header that may be suppressed.
*
* @param headerName the header name
*/
void permitResponseHeader(String headerName);

/**
* Unmodifiable request headers.
*
* @return the suppressed request headers
*/
Set<String> getSuppressedRequestHeaders();

/**
* Unmodifiable response headers.
*
* @return the suppressed response headers
*/
Set<String> getSuppressedResponseHeaders();


}
Expand Up @@ -34,9 +34,20 @@ public interface IConnectorFactory {
* @param api the managed API being invoked
* @param requiredAuthType the required authorization type
* @param hasDataPolicy if the policy chain contains a data policy
* @param connectorConfig the backend connector config. May be modified by policies.
* @return a connector to the back-end API
*/
public IApiConnector createConnector(ApiRequest request, Api api,
RequiredAuthType requiredAuthType, boolean hasDataPolicy);
public IApiConnector createConnector(ApiRequest request,
Api api,
RequiredAuthType requiredAuthType,
boolean hasDataPolicy,
IConnectorConfig connectorConfig);

/**
* Creates a connector config
* @param request the inbound API request
* @param api the managed API being invoked
* @return connectorConfig the backend connector config. May be modified by policies.
*/
public IConnectorConfig createConnectorConfig(ApiRequest request, Api api);
}
@@ -0,0 +1,132 @@
/*
* Copyright 2018 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.apiman.gateway.engine.impl;

import io.apiman.gateway.engine.IConnectorConfig;

import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;

/**
* Connector configuration with lazy initialisation of customised suppressions.
*
* Implementors should provide a static immutable default maps to {@link AbstractConnectorConfig}.
* This will be copied when needed into a mutable map (i.e. when the user adds/removes something).
*
* Future work could include caching common suppression maps, potentially.
*
* @author Marc Savy {@literal <marc@rhymewithgravy.com>}
*/
public abstract class AbstractConnectorConfig implements IConnectorConfig {

Set<String> suppressedRequestHeaders;
boolean modifiedDefaultRequestHeaders = false;

Set<String> suppressedResponseHeaders;
boolean modifiedDefaultResponseHeaders = false;

// TODO: cache common request and response suppression maps. Some quick and dirty way of looking it up?
// static LRUMap<String, Set<String>> cache = new LRUMap<String, Set<String>>(1000) {
// private static final long serialVersionUID = -18620070710843930L;
//
// @Override
// protected void handleRemovedElem(java.util.Map.Entry<String, Set<String>> eldest) {
// // Don't need to do anything.
// }
// };

/**
* Suppressed request headers
* Suppressed response headers
*
* @param suppressedRequestHeaders request headers to suppress
* @param suppressedResponseHeaders response headers to suppress
*/
public AbstractConnectorConfig(Set<String> suppressedRequestHeaders, Set<String> suppressedResponseHeaders) {
this.suppressedRequestHeaders = suppressedRequestHeaders;
this.suppressedResponseHeaders = suppressedResponseHeaders;
}

/**
* Construct no suppressed request nor response headers.
*/
public AbstractConnectorConfig() {
this.suppressedRequestHeaders = Collections.emptySet();
this.suppressedResponseHeaders = Collections.emptySet();
}

@Override
public void suppressRequestHeader(String headerName) {
if (!suppressedRequestHeaders.contains(headerName)) {
copyRequestMap();
suppressedRequestHeaders.add(headerName);
}
}

@Override
public void suppressResponseHeader(String headerName) {
if (!suppressedResponseHeaders.contains(headerName)) {
copyResponseMap();
suppressedResponseHeaders.add(headerName);
}
}

@Override
public void permitRequestHeader(String headerName) {
if (suppressedRequestHeaders.contains(headerName)) {
copyRequestMap();
suppressedRequestHeaders.remove(headerName);
}
}

@Override
public void permitResponseHeader(String headerName) {
if (suppressedResponseHeaders.contains(headerName)) {
copyResponseMap();
suppressedResponseHeaders.remove(headerName);
}
}

@Override
public Set<String> getSuppressedRequestHeaders() {
return Collections.unmodifiableSet(suppressedRequestHeaders);
}

@Override
public Set<String> getSuppressedResponseHeaders() {
return Collections.unmodifiableSet(suppressedResponseHeaders);
}

private void copyRequestMap() {
if (!modifiedDefaultRequestHeaders) {
TreeSet<String> reqCopy = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
reqCopy.addAll(suppressedRequestHeaders);
modifiedDefaultRequestHeaders = true;
this.suppressedRequestHeaders = reqCopy;
}
}

private void copyResponseMap() {
if (!modifiedDefaultResponseHeaders) {
TreeSet<String> resCopy = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
resCopy.addAll(suppressedResponseHeaders);
modifiedDefaultResponseHeaders = true;
this.suppressedResponseHeaders = resCopy;
}
}
}
Expand Up @@ -21,6 +21,7 @@
import io.apiman.gateway.engine.IApiConnectionResponse;
import io.apiman.gateway.engine.IApiConnector;
import io.apiman.gateway.engine.IApiRequestExecutor;
import io.apiman.gateway.engine.IConnectorConfig;
import io.apiman.gateway.engine.IConnectorFactory;
import io.apiman.gateway.engine.IEngineResult;
import io.apiman.gateway.engine.IMetrics;
Expand Down Expand Up @@ -244,9 +245,15 @@ public void execute() {
requestChain = createRequestChain((ApiRequest req) -> {
IConnectorInterceptor connectorInterceptor = context.getConnectorInterceptor();
IApiConnector connector;
IConnectorConfig connectorConfig = connectorFactory.createConnectorConfig(request, api);
context.setConnectorConfiguration(connectorConfig);

if (connectorInterceptor == null) {
connector = connectorFactory.createConnector(req, api,
RequiredAuthType.parseType(api), hasDataPolicy);
connector = connectorFactory.createConnector(req,
api,
RequiredAuthType.parseType(api),
hasDataPolicy,
connectorConfig);
} else {
connector = connectorInterceptor.createConnector();
}
Expand Down
Expand Up @@ -17,6 +17,7 @@

import io.apiman.common.logging.IApimanLogger;
import io.apiman.gateway.engine.IComponent;
import io.apiman.gateway.engine.IConnectorConfig;
import io.apiman.gateway.engine.beans.exceptions.ComponentNotFoundException;
import io.apiman.gateway.engine.beans.exceptions.InterceptorAlreadyRegisteredException;

Expand Down Expand Up @@ -79,4 +80,21 @@ public interface IPolicyContext {
* @return A logger associated with the conversation.
*/
IApimanLogger getLogger(Class<?> klazz);


/**
* Mutate connector attributes
* @return the connector configuration
*/
IConnectorConfig getConnectorConfiguration();

/**
* Set the connector configuration.
*
* Most usecases should simply mutate the existing configuration via
* {@link #getConnectorConfiguration()}.
*
* @param config the configuration
*/
void setConnectorConfiguration(IConnectorConfig config);
}
Expand Up @@ -20,6 +20,7 @@
import io.apiman.common.logging.IDelegateFactory;
import io.apiman.gateway.engine.IComponent;
import io.apiman.gateway.engine.IComponentRegistry;
import io.apiman.gateway.engine.IConnectorConfig;
import io.apiman.gateway.engine.beans.exceptions.ComponentNotFoundException;
import io.apiman.gateway.engine.beans.exceptions.InterceptorAlreadyRegisteredException;

Expand All @@ -39,13 +40,15 @@ public class PolicyContextImpl implements IPolicyContext {
// Using String instead of Class to avoid any accidental memory leak issues.
private final static Map<String, IApimanLogger> loggers = new HashMap<>();
private IConnectorInterceptor connectorInterceptor;
private IConnectorConfig connectorConfig;

/**
* Constructor.
* @param componentRegistry the component registry
* @param logFactory the log factory
*/
public PolicyContextImpl(IComponentRegistry componentRegistry, IDelegateFactory logFactory) {
public PolicyContextImpl(IComponentRegistry componentRegistry,
IDelegateFactory logFactory) {
this.componentRegistry = componentRegistry;
this.logFactory = logFactory;
}
Expand Down Expand Up @@ -118,4 +121,14 @@ public IApimanLogger getLogger(Class<?> klazz) {
}
}

@Override
public IConnectorConfig getConnectorConfiguration() {
return connectorConfig;
}

@Override
public void setConnectorConfiguration(IConnectorConfig connectorConfig) {
this.connectorConfig = connectorConfig;
}

}
Expand Up @@ -28,6 +28,7 @@
import io.apiman.gateway.engine.IApiRequestExecutor;
import io.apiman.gateway.engine.IApiRequestPathParser;
import io.apiman.gateway.engine.IComponentRegistry;
import io.apiman.gateway.engine.IConnectorConfig;
import io.apiman.gateway.engine.IConnectorFactory;
import io.apiman.gateway.engine.IEngine;
import io.apiman.gateway.engine.IEngineResult;
Expand Down Expand Up @@ -114,7 +115,7 @@ protected void registerBufferFactoryComponent() {
protected IConnectorFactory createConnectorFactory(IPluginRegistry pluginRegistry) {
return new IConnectorFactory() {
@Override
public IApiConnector createConnector(ApiRequest request, Api api, RequiredAuthType requiredAuthType, boolean hasDataPolicy) {
public IApiConnector createConnector(ApiRequest request, Api api, RequiredAuthType requiredAuthType, boolean hasDataPolicy, IConnectorConfig connectorConfig) {
Assert.assertEquals("test", api.getEndpointType());
Assert.assertEquals("test:endpoint", api.getEndpoint());
IApiConnector connector = new IApiConnector() {
Expand Down Expand Up @@ -179,6 +180,11 @@ public void abort(Throwable t) {
};
return connector;
}

@Override
public IConnectorConfig createConnectorConfig(ApiRequest request, Api api) {
return new TestConnectorConfigImpl();
}
};
}

Expand Down
@@ -0,0 +1,45 @@
/*
* Copyright 2018 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.apiman.gateway.engine.impl;

import io.apiman.gateway.engine.IConnectorConfig;

import java.util.Set;
import java.util.TreeSet;
/**
* Test implementation of {@link IConnectorConfig}
*
* @author Marc Savy {@literal <marc@rhymewithgravy.com>}
*/
@SuppressWarnings("nls")
public class TestConnectorConfigImpl extends AbstractConnectorConfig {
static final Set<String> REQUEST = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
static final Set<String> RESPONSE = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);

static {
REQUEST.add("Transfer-Encoding");
REQUEST.add("X-API-Key");
REQUEST.add("Host");

RESPONSE.add("Transfer-Encoding");
RESPONSE.add("Connection");
}

public TestConnectorConfigImpl() {
super(REQUEST, RESPONSE);
}
}

0 comments on commit ba38f09

Please sign in to comment.