Skip to content

Commit cc5236e

Browse files
authored
Merge pull request #2653 from srmppn/feature/keep-original-headers
Support for Case-Sensitive HTTP Headers in Karate Mock Server
2 parents bc1df10 + d17996f commit cc5236e

File tree

9 files changed

+207
-16
lines changed

9 files changed

+207
-16
lines changed

karate-core/src/main/java/com/intuit/karate/Main.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ public class Main implements Callable<Void> {
132132
@Option(names = {"-H", "--hook"}, split = ",", description = "class name of a RuntimeHook (or RuntimeHookFactory) to add")
133133
List<String> hookFactoryClassNames;
134134

135+
@Option(names = {"--keep-original-headers"}, description = "keeping original headers given in the mock scenario or configure")
136+
boolean keepOriginalHeaders;
137+
135138
//==========================================================================
136139
//
137140
public void addPath(String path) {
@@ -372,7 +375,8 @@ public Void call() throws Exception {
372375
RequestHandler handler = new RequestHandler(config);
373376
HttpServer.Builder builder = HttpServer
374377
.handler(handler)
375-
.corsEnabled(true);
378+
.corsEnabled(true)
379+
.keepOriginalHeaders(keepOriginalHeaders);
376380
if (ssl) {
377381
builder.https(port)
378382
.certFile(cert)
@@ -399,7 +403,8 @@ public Void call() throws Exception {
399403
});
400404
HttpServer.Builder builder = HttpServer.config(config)
401405
.local(false)
402-
.corsEnabled(true);
406+
.corsEnabled(true)
407+
.keepOriginalHeaders(keepOriginalHeaders);
403408
if (ssl) {
404409
builder.https(port);
405410
} else {
@@ -414,7 +419,8 @@ public Void call() throws Exception {
414419
.pathPrefix(prefix)
415420
.certFile(cert)
416421
.keyFile(key)
417-
.watch(watch);
422+
.watch(watch)
423+
.keepOriginalHeaders(keepOriginalHeaders);
418424
if (ssl) {
419425
builder.https(port);
420426
} else {

karate-core/src/main/java/com/intuit/karate/core/MockServer.java

+21-7
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,7 @@
2323
*/
2424
package com.intuit.karate.core;
2525

26-
import com.intuit.karate.http.HttpServer;
27-
import com.intuit.karate.http.HttpServerHandler;
28-
import com.intuit.karate.http.Request;
29-
import com.intuit.karate.http.Response;
30-
import com.intuit.karate.http.ServerHandler;
31-
import com.intuit.karate.http.SslContextFactory;
26+
import com.intuit.karate.http.*;
3227
import com.linecorp.armeria.server.HttpService;
3328
import com.linecorp.armeria.server.Server;
3429
import com.linecorp.armeria.server.ServerBuilder;
@@ -69,6 +64,7 @@ public static class Builder {
6964
Map<String, Object> args;
7065
String prefix = null;
7166
MockInterceptor interceptor = null;
67+
boolean keepOriginalHeaders;
7268

7369
public Builder watch(boolean value) {
7470
watch = value;
@@ -122,6 +118,11 @@ public Builder interceptor(MockInterceptor value) {
122118
return this;
123119
}
124120

121+
public Builder keepOriginalHeaders(boolean value) {
122+
keepOriginalHeaders = value;
123+
return this;
124+
}
125+
125126
public MockServer build() {
126127
ServerBuilder sb = Server.builder();
127128
sb.requestTimeoutMillis(0);
@@ -135,8 +136,21 @@ public MockServer build() {
135136
} else {
136137
sb.http(port);
137138
}
139+
138140
ServerHandler handler = watch ? new ReloadingMockHandler(features, args, prefix, interceptor) : new MockHandler(prefix, features, args, interceptor);
139-
HttpService service = new HttpServerHandler(handler);
141+
142+
HttpServerHandler.Builder serverHandlerBuilder = HttpServerHandler.Builder.builder();
143+
serverHandlerBuilder.handler(handler);
144+
145+
if (keepOriginalHeaders) {
146+
HttpHeaderTracking headerTracking = new GenericHttpHeaderTracking();
147+
sb.http1HeaderNaming(http2HeaderName ->
148+
headerTracking.getOriginalHeader(String.valueOf(http2HeaderName)));
149+
150+
serverHandlerBuilder.httpHeaderTracking(headerTracking);
151+
}
152+
153+
HttpService service = serverHandlerBuilder.build();
140154
sb.service("prefix:" + (prefix == null ? "/" : prefix), service);
141155
return new MockServer(sb);
142156
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.intuit.karate.http;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
public class GenericHttpHeaderTracking implements HttpHeaderTracking {
7+
8+
private final Map<String, String> httpHeaderReference = new HashMap<>();
9+
10+
@Override
11+
public void putHeaderReference(String originalHeader) {
12+
if (originalHeader == null) {
13+
return;
14+
}
15+
16+
httpHeaderReference.put(originalHeader.toLowerCase(), originalHeader);
17+
}
18+
19+
@Override
20+
public String getOriginalHeader(String headerReference) {
21+
if (headerReference == null) {
22+
return null;
23+
}
24+
25+
return httpHeaderReference.getOrDefault(headerReference.toLowerCase(), headerReference);
26+
}
27+
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.intuit.karate.http;
2+
3+
public interface HttpHeaderTracking {
4+
5+
void putHeaderReference(String originalHeader);
6+
7+
String getOriginalHeader(String headerReference);
8+
9+
}

karate-core/src/main/java/com/intuit/karate/http/HttpServer.java

+21-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ public static class Builder { // TODO fix code duplication with MockServer
5656
File keyFile;
5757
boolean corsEnabled;
5858
ServerHandler handler;
59-
59+
boolean keepOriginalHeaders;
60+
6061
public Builder local(boolean value) {
6162
local = value;
6263
return this;
@@ -93,6 +94,11 @@ public Builder handler(ServerHandler value) {
9394
return this;
9495
}
9596

97+
public Builder keepOriginalHeaders(boolean value) {
98+
keepOriginalHeaders = value;
99+
return this;
100+
}
101+
96102
public HttpServer build() {
97103
ServerBuilder sb = Server.builder();
98104
sb.requestTimeoutMillis(0);
@@ -115,11 +121,23 @@ public HttpServer build() {
115121
sb.http(port);
116122
}
117123
}
118-
HttpService service = new HttpServerHandler(handler);
124+
125+
HttpServerHandler.Builder serverHandlerBuilder = HttpServerHandler.Builder.builder();
126+
serverHandlerBuilder.handler(handler);
127+
128+
if (keepOriginalHeaders) {
129+
HttpHeaderTracking headerTracking = new GenericHttpHeaderTracking();
130+
sb.http1HeaderNaming(http2HeaderName ->
131+
headerTracking.getOriginalHeader(String.valueOf(http2HeaderName)));
132+
133+
serverHandlerBuilder.httpHeaderTracking(headerTracking);
134+
}
135+
136+
HttpService service = serverHandlerBuilder.build();
119137
if (corsEnabled) {
120138
service = service.decorate(CorsService.builderForAnyOrigin().allowAllRequestHeaders(true).newDecorator());
121139
}
122-
sb.service("prefix:/", service);
140+
sb.service("prefix:/", service);
123141
return new HttpServer(sb);
124142
}
125143

karate-core/src/main/java/com/intuit/karate/http/HttpServerHandler.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ public class HttpServerHandler implements HttpService {
4848

4949
private final ServerHandler handler;
5050

51-
public HttpServerHandler(ServerHandler handler) {
51+
private final HttpHeaderTracking httpHeaderTracking;
52+
53+
public HttpServerHandler(ServerHandler handler, HttpHeaderTracking httpHeaderTracking) {
5254
this.handler = handler;
55+
this.httpHeaderTracking = httpHeaderTracking;
5356
}
5457

5558
@Override
@@ -94,7 +97,12 @@ private HttpResponse toResponse(ServiceRequestContext ctx, Response response) {
9497
ResponseHeadersBuilder rhb = ResponseHeaders.builder(response.getStatus());
9598
Map<String, List<String>> headers = response.getHeaders();
9699
if (headers != null) {
97-
headers.forEach((k, v) -> rhb.add(k, v));
100+
headers.forEach((k, v) -> {
101+
rhb.add(k, v);
102+
if (httpHeaderTracking != null) {
103+
httpHeaderTracking.putHeaderReference(k);
104+
}
105+
});
98106
}
99107
HttpResponse hr = HttpResponse.of(rhb.build(), HttpData.wrap(body));
100108
if (response.getDelay() > 0) {
@@ -104,4 +112,29 @@ private HttpResponse toResponse(ServiceRequestContext ctx, Response response) {
104112
}
105113
}
106114

115+
public static class Builder {
116+
117+
private ServerHandler handler;
118+
119+
private HttpHeaderTracking httpHeaderTracking;
120+
121+
public static Builder builder() {
122+
return new Builder();
123+
}
124+
125+
public Builder handler(ServerHandler value) {
126+
handler = value;
127+
return this;
128+
}
129+
130+
public Builder httpHeaderTracking(HttpHeaderTracking value) {
131+
httpHeaderTracking = value;
132+
return this;
133+
}
134+
135+
public HttpServerHandler build() {
136+
return new HttpServerHandler(handler, httpHeaderTracking);
137+
}
138+
}
139+
107140
}

karate-core/src/test/java/com/intuit/karate/MainTest.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,10 @@ void testDebug() {
2121
Main options = Main.parseKarateArgs(List.of("-d"));
2222
assertEquals(0, options.debugPort);
2323
}
24-
24+
25+
@Test
26+
void testKeepOriginalHeaders() {
27+
Main options = Main.parseKarateArgs(List.of("--keep-original-headers"));
28+
assertEquals(true, options.keepOriginalHeaders);
29+
}
2530
}

karate-core/src/test/java/com/intuit/karate/core/HttpMockHandlerTest.java

+24
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ HttpRequestBuilder handle() {
3636
return http;
3737
}
3838

39+
HttpRequestBuilder handleWithOriginalHeaders() {
40+
handler = new MockHandler(mock.build());
41+
server = HttpServer.handler(handler).keepOriginalHeaders(true).build();
42+
ScenarioEngine se = ScenarioEngine.forTempUse(HttpClientFactory.DEFAULT);
43+
ApacheHttpClient client = new ApacheHttpClient(se);
44+
http = new HttpRequestBuilder(client);
45+
http.url("http://localhost:" + server.getPort());
46+
return http;
47+
}
48+
3949
FeatureBuilder background(String... lines) {
4050
mock = FeatureBuilder.background(lines);
4151
return mock;
@@ -96,4 +106,18 @@ void testConfigureResponseHeaders() {
96106
match(response.getHeader("Content-Type"), "text/html");
97107
}
98108

109+
@Test
110+
void testKeepOriginalResponseHeaders() {
111+
background("configure responseHeaders = { 'X-Special-Header': 'test1' }")
112+
.scenario(
113+
"pathMatches('/hello')",
114+
"def responseHeaders = { 'X-Custom-Header': 'test2' }",
115+
"def response = ''");
116+
response = handleWithOriginalHeaders().path("/hello").invoke("get");
117+
match(response.getHeader("X-Special-Header"), "test1");
118+
match(response.getHeader("X-Custom-Header"), "test2");
119+
120+
matchContains(response.getHeaders().keySet(), "X-Special-Header");
121+
matchContains(response.getHeaders().keySet(), "X-Custom-Header");
122+
}
99123
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.intuit.karate.http;
2+
3+
import org.junit.jupiter.api.Assertions;
4+
import org.junit.jupiter.api.BeforeEach;
5+
import org.junit.jupiter.api.Test;
6+
7+
class GenericHttpHeaderTrackingTest {
8+
9+
private GenericHttpHeaderTracking httpHeaderTracking;
10+
11+
@BeforeEach
12+
void beforeEach() {
13+
httpHeaderTracking = new GenericHttpHeaderTracking();
14+
}
15+
16+
@Test
17+
void testPutHeader() {
18+
String header = "X-Special-Header";
19+
20+
Assertions.assertDoesNotThrow(() -> httpHeaderTracking.putHeaderReference(header));
21+
}
22+
23+
@Test
24+
void testPutHeaderWithNull() {
25+
String header = null;
26+
27+
Assertions.assertDoesNotThrow(() -> httpHeaderTracking.putHeaderReference(header));
28+
}
29+
30+
@Test
31+
void testGetOriginalHeader() {
32+
String header = "X-Special-Header";
33+
httpHeaderTracking.putHeaderReference(header);
34+
35+
String result = httpHeaderTracking.getOriginalHeader(header);
36+
Assertions.assertEquals(header, result);
37+
}
38+
39+
@Test
40+
void testGetOriginalHeaderWithoutExistingHeaderInTracking() {
41+
String header = "X-Special-Header";
42+
43+
String result = httpHeaderTracking.getOriginalHeader(header);
44+
Assertions.assertEquals(header, result);
45+
}
46+
47+
@Test
48+
void testGetOriginalHeaderWithNull() {
49+
String header = null;
50+
51+
String result = httpHeaderTracking.getOriginalHeader(header);
52+
Assertions.assertNull(result);
53+
}
54+
}

0 commit comments

Comments
 (0)