Scheme & Protocol

This commit is contained in:
Marcin Grzejszczak
2020-09-15 09:11:36 +02:00
committed by Marcin Grzejszczak
parent 2121b0b1b4
commit 5e5782f736
5 changed files with 142 additions and 36 deletions

View File

@@ -41,6 +41,7 @@ class CustomModeGiven implements Given, BodyMethodVisitor, CustomModeAcceptor {
new CustomModeRequestGiven(blockBuilder, generatedClassMetaData)));
this.bodyGivens.addAll(
Arrays.asList(new CustomModeMethodWithUrlGiven(blockBuilder, bodyParser),
new CustomModeSchemeProtocolGiven(blockBuilder),
new CustomModeHeadersGiven(blockBuilder),
new CustomModeCookiesGiven(blockBuilder),
new CustomModeBodyGiven(blockBuilder, generatedClassMetaData,

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* 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
*
* https://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 org.springframework.cloud.contract.verifier.builder;
import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.verifier.file.SingleContractMetadata;
import org.springframework.cloud.contract.verifier.http.ContractVerifierHttpMetadata;
class CustomModeSchemeProtocolGiven implements Given {
private final BlockBuilder blockBuilder;
CustomModeSchemeProtocolGiven(BlockBuilder blockBuilder) {
this.blockBuilder = blockBuilder;
}
@Override
public MethodVisitor<Given> apply(SingleContractMetadata metadata) {
Contract contract = metadata.getContract();
ContractVerifierHttpMetadata httpMetadata = ContractVerifierHttpMetadata
.fromMetadata(contract.getMetadata());
this.blockBuilder
.addIndented(".scheme(\"" + httpMetadata.getScheme().name() + "\")")
.addEmptyLine();
this.blockBuilder.addIndented(
".protocol(\"" + httpMetadata.getProtocol().toString() + "\")");
return this;
}
@Override
public boolean accept(SingleContractMetadata metadata) {
Request request = metadata.getContract().getRequest();
return request != null;
}
}

View File

@@ -42,12 +42,12 @@ public class ContractVerifierHttpMetadata implements SpringCloudContractMetadata
/**
* Scheme used for HTTP communication.
*/
private Scheme scheme;
private Scheme scheme = Scheme.HTTP;
/**
* Protocol used for HTTP communication.
*/
private Protocol protocol;
private Protocol protocol = Protocol.HTTP_1_1;
@NonNull
public static ContractVerifierHttpMetadata fromMetadata(
@@ -117,7 +117,7 @@ public class ContractVerifierHttpMetadata implements SpringCloudContractMetadata
}
/**
* Taken from OKHttp's Protocol.
* Taken from OKHttp's Protocol {@link okhttp3.Protocol}.
*/
public enum Protocol {

View File

@@ -18,10 +18,10 @@ package org.springframework.cloud.contract.verifier.http;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -33,8 +33,8 @@ import okhttp3.RequestBody;
import org.jetbrains.annotations.Nullable;
/**
* {@link HttpVerifier} implementation that uses {@link OkHttpClient}.
* Has an inbuilt support for GRPC.
* {@link HttpVerifier} implementation that uses {@link OkHttpClient}. Has an inbuilt
* support for GRPC.
*
* Warning! This API is experimental and can change in time.
*
@@ -43,24 +43,36 @@ import org.jetbrains.annotations.Nullable;
*/
public class OkHttpHttpVerifier implements HttpVerifier {
private final String url;
private final String hostAndPort;
public OkHttpHttpVerifier(String url) {
this.url = url;
/**
* @param hostAndPort - don't pass the scheme, it will be resolved from
* {@link Request#scheme()}. E.g. pass {@code localhost:1234}.
*/
public OkHttpHttpVerifier(String hostAndPort) {
this.hostAndPort = hostAndPort;
}
@Override
public Response exchange(Request request) {
String requestContentType = request.contentType();
// TODO: Resolve protocol and scheme from contract?
OkHttpClient client = new OkHttpClient.Builder()
.protocols(protocols(requestContentType)).build();
okhttp3.Request req = new okhttp3.Request.Builder()
.url(this.url + (request.path().startsWith("/") ? request.path()
: "/" + request.path()))
.method(request.method().name(), requestBody(request, requestContentType))
.headers(Headers.of(stringTyped(request.headers()))) // TODO: Add cookies
.protocols(Collections
.singletonList(toProtocol(request.protocol().toString())))
.build();
Map<String, String> headers = stringTyped(request.headers());
if (!request.cookies().isEmpty()) {
headers.put("Set-Cookie",
request.cookies().entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue().toString())
.collect(Collectors.joining(";")));
}
okhttp3.Request req = new okhttp3.Request.Builder()
.url(request.scheme().name().toLowerCase() + ":" + this.hostAndPort
+ (request.path().startsWith("/") ? request.path()
: "/" + request.path()))
.method(request.method().name(), requestBody(request, requestContentType))
.headers(Headers.of(headers)).build();
try (okhttp3.Response res = client.newCall(req).execute()) {
return response(res);
}
@@ -69,29 +81,27 @@ public class OkHttpHttpVerifier implements HttpVerifier {
}
}
private List<Protocol> protocols(String requestContentType) {
if (this.url.startsWith("https")) {
return Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1);
private Protocol toProtocol(String string) {
try {
return Protocol.get(string);
}
else if (isGrpc(requestContentType)) {
return Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE);
catch (IOException e) {
throw new IllegalStateException(e);
}
return Collections.singletonList(Protocol.HTTP_1_1);
}
private Response response(okhttp3.Response res) throws IOException {
byte[] responseBody = responseBody(res);
// String contentType = res.headers().get("Content-Type");
// TODO: Response body in the test / contract should already be properly encoded
// if (contentType != null && isGrpc(contentType) && responseBody != null) {
// responseBody = grpcResponseBody(responseBody);
// }
return Response.builder().body(responseBody).statusCode(res.code())
.headers(withSingleHeader(res))
// TODO: Add cookies
// .cookies(res.headers().values("Set-Cookie").stream().map(s ->
// s.split(";")).flatMap(Arrays::stream).collect(Collectors.toMap(o -> o.
// , e -> e.getValue().get(0), (a,b) -> a, HashMap::new))))
.headers(withSingleHeader(res)).cookies(res.headers().values("Set-Cookie")
.stream().flatMap(s -> Arrays.stream(s.split(";"))).map(s -> {
String[] singleCookie = s.split("=");
return new AbstractMap.SimpleEntry<>(singleCookie[0],
singleCookie.length > 1 ? singleCookie[1] : "");
})
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey,
AbstractMap.SimpleEntry::getValue, (a, b) -> a,
HashMap::new)))
.build();
}

View File

@@ -32,6 +32,10 @@ import org.springframework.cloud.contract.spec.internal.HttpMethods;
*/
public class Request {
private final ContractVerifierHttpMetadata.Protocol protocol;
private final ContractVerifierHttpMetadata.Scheme scheme;
private final HttpMethods.HttpMethod method;
private final String path;
@@ -42,8 +46,12 @@ public class Request {
private final Map<String, Object> cookies;
Request(HttpMethods.HttpMethod method, String path, Body body,
Map<String, Object> headers, Map<String, Object> cookies) {
Request(ContractVerifierHttpMetadata.Protocol protocol,
ContractVerifierHttpMetadata.Scheme scheme, HttpMethods.HttpMethod method,
String path, Body body, Map<String, Object> headers,
Map<String, Object> cookies) {
this.protocol = protocol;
this.scheme = scheme;
this.method = method;
this.path = path;
this.body = body;
@@ -64,6 +72,20 @@ public class Request {
return value.toString();
}
/**
* @return {@link ContractVerifierHttpMetadata.Protocol}
*/
public ContractVerifierHttpMetadata.Protocol protocol() {
return this.protocol;
}
/**
* @return {@link ContractVerifierHttpMetadata.Scheme}
*/
public ContractVerifierHttpMetadata.Scheme scheme() {
return this.scheme;
}
/**
* @return HTTP method
*/
@@ -195,6 +217,10 @@ public class Request {
final String path;
ContractVerifierHttpMetadata.Protocol protocol = ContractVerifierHttpMetadata.Protocol.HTTP_1_1;
ContractVerifierHttpMetadata.Scheme scheme = ContractVerifierHttpMetadata.Scheme.HTTP;
Body body;
Map<String, Object> headers = new HashMap<>();
@@ -206,6 +232,24 @@ public class Request {
this.path = path;
}
/**
* @param scheme text representation of a scheme
* @return builder
*/
public Request.Builder scheme(String scheme) {
this.scheme = ContractVerifierHttpMetadata.Scheme.fromString(scheme);
return this;
}
/**
* @param protocol text representation of a protocol
* @return builder
*/
public Request.Builder protocol(String protocol) {
this.protocol = ContractVerifierHttpMetadata.Protocol.fromString(protocol);
return this;
}
/**
* @param body HTTP body
* @return builder
@@ -247,8 +291,8 @@ public class Request {
* @return built {@link Request}
*/
public Request build() {
return new Request(this.method, this.path, this.body, this.headers,
this.cookies);
return new Request(this.protocol, this.scheme, this.method, this.path,
this.body, this.headers, this.cookies);
}
}