Skip to content

Commit a344fef

Browse files
committed
feat: add upstream request processing
1 parent 88175fa commit a344fef

9 files changed

Lines changed: 256 additions & 0 deletions

File tree

context/src/main/java/com/mx/path/core/context/RequestContext.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.function.Consumer;
44

55
import lombok.AllArgsConstructor;
6+
import lombok.Builder;
67
import lombok.Data;
78
import lombok.Setter;
89
import lombok.experimental.SuperBuilder;
@@ -43,9 +44,20 @@ public static RequestContext current() {
4344
private String userGuid;
4445
@Deprecated
4546
private String userId;
47+
48+
/**
49+
* Headers for current request
50+
*/
4651
private SingleValueMap<String, Object> headers;
52+
53+
/**
54+
* Function parameters for current request
55+
*/
4756
private SingleValueMap<String, Object> params;
4857

58+
@Builder.Default
59+
private UpstreamRequestConfiguration upstreamRequestConfiguration = new UpstreamRequestConfiguration();
60+
4961
public abstract static class RequestContextBuilder<C extends RequestContext, B extends RequestContext.RequestContextBuilder<C, B>> {
5062

5163
private SingleValueMap<String, Object> headers = new SingleValueMap<>();
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.mx.path.core.context;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Data;
9+
10+
import com.mx.path.core.common.collection.SingleValueMap;
11+
12+
/**
13+
* Configuration to be applied to all upstream requests within {@link RequestContext#getUpstreamRequestConfiguration()}
14+
*/
15+
@Data
16+
@Builder
17+
@AllArgsConstructor
18+
public class UpstreamRequestConfiguration {
19+
20+
public UpstreamRequestConfiguration() {
21+
forwardedRequestHeaders = new SingleValueMap<>();
22+
upstreamRequestProcessors = new ArrayList<>();
23+
}
24+
25+
/**
26+
* Headers to be forwarded in all upstream {@link com.mx.path.core.common.connect.Request} executes
27+
*/
28+
private SingleValueMap<String, Object> forwardedRequestHeaders;
29+
30+
/**
31+
* Instances of upstream processors that will be invoked in all upstream {@link com.mx.path.core.common.connect.Request} executes
32+
*/
33+
private List<UpstreamRequestProcessor> upstreamRequestProcessors;
34+
35+
public static class UpstreamRequestConfigurationBuilder {
36+
private SingleValueMap<String, Object> forwardedRequestHeaders = new SingleValueMap<>();
37+
private List<UpstreamRequestProcessor> upstreamRequestProcessors = new ArrayList<>();
38+
39+
public final UpstreamRequestConfigurationBuilder forwardedHeader(String key, String value) {
40+
forwardedRequestHeaders.put(key, value);
41+
42+
return this;
43+
}
44+
45+
public final UpstreamRequestConfigurationBuilder upstreamRequestProcessor(UpstreamRequestProcessor processor) {
46+
upstreamRequestProcessors.add(processor);
47+
48+
return this;
49+
}
50+
}
51+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.mx.path.core.context;
2+
3+
import java.util.function.BiConsumer;
4+
5+
import lombok.Builder;
6+
import lombok.Data;
7+
8+
import com.mx.path.core.common.connect.Request;
9+
import com.mx.path.core.common.connect.Response;
10+
11+
@Data
12+
@Builder
13+
public class UpstreamRequestProcessor {
14+
private BiConsumer<Request<?, ?>, Response<?, ?>> after;
15+
private BiConsumer<Request<?, ?>, Response<?, ?>> before;
16+
17+
public final void executeAfter(Request<?, ?> request, Response<?, ?> response) {
18+
if (after != null) {
19+
after.accept(request, response);
20+
}
21+
}
22+
23+
public final void executeBefore(Request<?, ?> request, Response<?, ?> response) {
24+
if (before != null) {
25+
before.accept(request, response);
26+
}
27+
}
28+
}

context/src/test/groovy/com/mx/path/core/context/RequestContextTest.groovy

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ class RequestContextTest extends Specification {
6464
subject.getHeaders().get("h1") == "v1"
6565
subject.getParams().get("p1") == "v1"
6666
subject.getParams().get("p2") == "v2"
67+
68+
and: "UpstreamRequestConfiguration is initialized"
69+
subject.getUpstreamRequestConfiguration() != null
6770
}
6871

6972
def "withSelfClearing"() {
@@ -95,6 +98,7 @@ class RequestContextTest extends Specification {
9598
def "setters"() {
9699
given:
97100
def subject = RequestContext.builder().build()
101+
def upstreamRequestConfiguration = new UpstreamRequestConfiguration()
98102

99103
when:
100104
subject.setClientGuid("clientAF")
@@ -103,6 +107,7 @@ class RequestContextTest extends Specification {
103107
subject.setOriginatingIP("127.0.0.1")
104108
subject.setPath("/accounts")
105109
subject.setSessionTraceId("abcde")
110+
subject.setUpstreamRequestConfiguration(upstreamRequestConfiguration)
106111
subject.setUserGuid("userAF")
107112
subject.getHeaders().put("h2", "v2")
108113
subject.getHeaders().put("h1", "v1")
@@ -116,6 +121,7 @@ class RequestContextTest extends Specification {
116121
subject.getOriginatingIP() == "127.0.0.1"
117122
subject.getPath() == "/accounts"
118123
subject.getSessionTraceId() == "abcde"
124+
subject.getUpstreamRequestConfiguration() == upstreamRequestConfiguration
119125
subject.getUserGuid() == "userAF"
120126
subject.getHeaders().get("h2") == "v2"
121127
subject.getHeaders().get("h1") == "v1"
@@ -126,6 +132,7 @@ class RequestContextTest extends Specification {
126132
def "toBuilder"() {
127133
given:
128134
def subject = RequestContext.builder().build()
135+
def upstreamRequestConfiguration = new UpstreamRequestConfiguration()
129136

130137
when:
131138
subject.setClientGuid("clientAF")
@@ -134,6 +141,7 @@ class RequestContextTest extends Specification {
134141
subject.setOriginatingIP("127.0.0.1")
135142
subject.setPath("/accounts")
136143
subject.setSessionTraceId("abcde")
144+
subject.setUpstreamRequestConfiguration(upstreamRequestConfiguration)
137145
subject.setUserGuid("userAF")
138146
subject.getHeaders().put("h2", "v2")
139147
subject.getHeaders().put("h1", "v1")
@@ -148,6 +156,7 @@ class RequestContextTest extends Specification {
148156
result.getOriginatingIP() == "127.0.0.1"
149157
result.getPath() == "/accounts"
150158
result.getSessionTraceId() == "abcde"
159+
subject.getUpstreamRequestConfiguration() == upstreamRequestConfiguration
151160
result.getUserGuid() == "userAF"
152161
result.getHeaders().get("h2") == "v2"
153162
result.getHeaders().get("h1") == "v1"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.mx.path.core.context
2+
3+
import spock.lang.Specification
4+
5+
class UpstreamRequestConfigurationTest extends Specification {
6+
def "constructor"() {
7+
when:
8+
def subject = new UpstreamRequestConfiguration()
9+
10+
then:
11+
subject.getForwardedRequestHeaders().isEmpty()
12+
subject.getUpstreamRequestProcessors().isEmpty()
13+
}
14+
15+
def "builder"() {
16+
when: "take defaults"
17+
def subject = UpstreamRequestConfiguration.builder().build()
18+
19+
then: "collections are initialized"
20+
subject.getForwardedRequestHeaders().isEmpty()
21+
subject.getUpstreamRequestProcessors().isEmpty()
22+
23+
when:
24+
def upstreamRequestProcessors = UpstreamRequestProcessor.builder().build()
25+
subject = UpstreamRequestConfiguration.builder()
26+
.forwardedHeader("h1", "v1")
27+
.upstreamRequestProcessor(upstreamRequestProcessors)
28+
.build()
29+
30+
then:
31+
subject.getForwardedRequestHeaders().get("h1") == "v1"
32+
subject.getUpstreamRequestProcessors() == [upstreamRequestProcessors]
33+
}
34+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.mx.path.core.context
2+
3+
import spock.lang.Specification
4+
5+
class UpstreamRequestProcessorTest extends Specification {
6+
def "builder"() {
7+
when:
8+
def subject = UpstreamRequestProcessor.builder().before({}).after({}).build()
9+
10+
then:
11+
subject.getBefore() != null
12+
subject.getAfter() != null
13+
}
14+
15+
def "executeBefore"() {
16+
given:
17+
def beforeCalled = false
18+
19+
def subject = UpstreamRequestProcessor.builder().before({ request, response ->
20+
beforeCalled = true
21+
}).build()
22+
23+
when:
24+
subject.executeBefore(null, null)
25+
subject.executeAfter(null, null)
26+
27+
then:
28+
beforeCalled
29+
}
30+
31+
def "executeAfter"() {
32+
given:
33+
def afterCalled = false
34+
35+
def subject = UpstreamRequestProcessor.builder().after({ request, response ->
36+
afterCalled = true
37+
}).build()
38+
39+
when:
40+
subject.executeBefore(null, null)
41+
subject.executeAfter(null, null)
42+
43+
then:
44+
afterCalled
45+
}
46+
}

gateway/src/main/java/com/mx/path/gateway/configuration/AccessorStackConfigurator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.mx.path.gateway.connect.filter.RequestFinishedFilter;
3636
import com.mx.path.gateway.connect.filter.TracingFilter;
3737
import com.mx.path.gateway.connect.filter.UpstreamRequestEventFilter;
38+
import com.mx.path.gateway.connect.filter.UpstreamRequestProcessorFilter;
3839

3940
import org.slf4j.Logger;
4041
import org.slf4j.LoggerFactory;
@@ -198,6 +199,7 @@ private void buildConnections(ObjectMap map, AccessorConfiguration.AccessorConfi
198199
connection.baseRequestFilter(new UpstreamRequestEventFilter());
199200
connection.baseRequestFilter(new ErrorHandlerFilter());
200201
connection.baseRequestFilter(new CallbacksFilter());
202+
connection.baseRequestFilter(new UpstreamRequestProcessorFilter());
201203
connection.baseRequestFilter(new RequestFinishedFilter());
202204
connection.baseRequestFilter(new FaultTolerantRequestFilter());
203205

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.mx.path.gateway.connect.filter;
2+
3+
import java.util.List;
4+
5+
import com.mx.path.core.common.connect.Request;
6+
import com.mx.path.core.common.connect.RequestFilterBase;
7+
import com.mx.path.core.common.connect.Response;
8+
import com.mx.path.core.context.RequestContext;
9+
import com.mx.path.core.context.UpstreamRequestProcessor;
10+
11+
public class UpstreamRequestProcessorFilter extends RequestFilterBase {
12+
@Override
13+
public final void execute(Request request, Response response) {
14+
List<UpstreamRequestProcessor> processors = null;
15+
16+
if (RequestContext.current() != null && RequestContext.current().getUpstreamRequestConfiguration() != null) {
17+
processors = RequestContext.current().getUpstreamRequestConfiguration().getUpstreamRequestProcessors();
18+
}
19+
20+
if (processors != null && !processors.isEmpty()) {
21+
processors.forEach(processor -> {
22+
processor.executeBefore(request, response);
23+
});
24+
25+
next(request, response);
26+
27+
processors.forEach(processor -> {
28+
processor.executeAfter(request, response);
29+
});
30+
} else {
31+
next(request, response);
32+
}
33+
}
34+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.mx.path.gateway.connect.filter
2+
3+
import com.mx.path.core.context.RequestContext
4+
import com.mx.path.core.context.UpstreamRequestConfiguration
5+
import com.mx.path.core.context.UpstreamRequestProcessor
6+
7+
import spock.lang.Specification
8+
9+
class UpstreamRequestProcessorFilterTest extends Specification {
10+
UpstreamRequestProcessorFilter subject
11+
12+
def setup() {
13+
subject = new UpstreamRequestProcessorFilter()
14+
}
15+
16+
def "executes before and after"() {
17+
given:
18+
def beforeCalled = false
19+
def afterCalled = false
20+
21+
RequestContext.builder()
22+
.upstreamRequestConfiguration(
23+
UpstreamRequestConfiguration.builder()
24+
.upstreamRequestProcessor(UpstreamRequestProcessor.builder()
25+
.before({ request, response -> beforeCalled = true})
26+
.after({ request, response -> afterCalled = true})
27+
.build()
28+
).build()
29+
)
30+
.build()
31+
.register()
32+
33+
when:
34+
subject.execute(null, null)
35+
36+
then:
37+
beforeCalled
38+
afterCalled
39+
}
40+
}

0 commit comments

Comments
 (0)