diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeGiven.java index ee6bf48658..d5cb0bd1f0 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeGiven.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeGiven.java @@ -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, diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeSchemeProtocolGiven.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeSchemeProtocolGiven.java new file mode 100644 index 0000000000..f794482144 --- /dev/null +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/builder/CustomModeSchemeProtocolGiven.java @@ -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 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; + } + +} diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/ContractVerifierHttpMetadata.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/ContractVerifierHttpMetadata.java index 57e37e813d..29c3578c24 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/ContractVerifierHttpMetadata.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/ContractVerifierHttpMetadata.java @@ -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 { diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/OkHttpHttpVerifier.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/OkHttpHttpVerifier.java index 2ff34ec0ae..2ce4dd2681 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/OkHttpHttpVerifier.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/OkHttpHttpVerifier.java @@ -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 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 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(); } diff --git a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Request.java b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Request.java index 560bb46145..30e22b1d2a 100644 --- a/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Request.java +++ b/spring-cloud-contract-verifier/src/main/java/org/springframework/cloud/contract/verifier/http/Request.java @@ -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 cookies; - Request(HttpMethods.HttpMethod method, String path, Body body, - Map headers, Map cookies) { + Request(ContractVerifierHttpMetadata.Protocol protocol, + ContractVerifierHttpMetadata.Scheme scheme, HttpMethods.HttpMethod method, + String path, Body body, Map headers, + Map 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 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); } }