You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2020/12/07 01:53:41 UTC
[servicecomb-java-chassis] branch master updated: [SCB-2146] add
RestClientDecoder (#2104)
This is an automated email from the ASF dual-hosted git repository.
liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git
The following commit(s) were added to refs/heads/master by this push:
new 013ad6d [SCB-2146] add RestClientDecoder (#2104)
013ad6d is described below
commit 013ad6db0c5b7c60d91b021031a480065c6bf334
Author: wujimin <wu...@huawei.com>
AuthorDate: Mon Dec 7 09:53:32 2020 +0800
[SCB-2146] add RestClientDecoder (#2104)
---
.../transport/rest/client/RestClientDecoder.java | 119 +++++++++++++++++++++
.../rest/client/RestClientExceptionCodes.java | 5 +
.../rest/client/RestClientDecoderTest.java | 110 +++++++++++++++++++
.../transport/rest/client/RestClientTestBase.java | 3 +-
4 files changed, 235 insertions(+), 2 deletions(-)
diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientDecoder.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientDecoder.java
new file mode 100644
index 0000000..3428a45
--- /dev/null
+++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientDecoder.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.transport.rest.client;
+
+import static org.apache.servicecomb.transport.rest.client.RestClientExceptionCodes.FAILED_TO_DECODE_REST_FAIL_RESPONSE;
+import static org.apache.servicecomb.transport.rest.client.RestClientExceptionCodes.FAILED_TO_DECODE_REST_SUCCESS_RESPONSE;
+
+import javax.ws.rs.core.HttpHeaders;
+
+import org.apache.servicecomb.common.rest.codec.produce.ProduceProcessor;
+import org.apache.servicecomb.common.rest.codec.produce.ProduceProcessorManager;
+import org.apache.servicecomb.core.Invocation;
+import org.apache.servicecomb.core.exception.Exceptions;
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.databind.JavaType;
+
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpClientRequest;
+
+@Component
+public class RestClientDecoder {
+ private static final Logger LOGGER = LoggerFactory.getLogger(RestClientDecoder.class);
+
+ public Response decode(Invocation invocation, Response response) {
+ if (response.getResult() instanceof Buffer) {
+ Object result = extractBody(invocation, response);
+ response.entity(result);
+
+ if (response.isFailed()) {
+ throw Exceptions.create(response.getStatus(), response.getResult());
+ }
+ }
+
+ return response;
+ }
+
+ protected Object extractBody(Invocation invocation, Response response) {
+ ProduceProcessor produceProcessor = safeFindProduceProcessor(invocation, response);
+ JavaType responseType = invocation.findResponseType(response.getStatusCode());
+
+ try {
+ return produceProcessor.decodeResponse((Buffer) response.getResult(), responseType);
+ } catch (Exception e) {
+ throw createDecodeException(invocation, response, e);
+ }
+ }
+
+ private ProduceProcessor safeFindProduceProcessor(Invocation invocation, Response response) {
+ RestClientTransportContext transportContext = invocation.getTransportContext();
+
+ String contentType = extractContentType(response);
+ ProduceProcessor produceProcessor = transportContext.getRestOperationMeta().findProduceProcessor(contentType);
+ if (produceProcessor != null) {
+ return produceProcessor;
+ }
+
+ HttpClientRequest httpClientRequest = transportContext.getHttpClientRequest();
+ LOGGER.warn(
+ "method {}, endpoint {}, uri {}, statusCode {}, reasonPhrase {}, response content-type {} is not supported in operation.",
+ httpClientRequest.method(),
+ invocation.getEndpoint().getEndpoint(),
+ httpClientRequest.absoluteURI(),
+ response.getStatusCode(),
+ response.getReasonPhrase(),
+ response.getHeader(HttpHeaders.CONTENT_TYPE));
+
+ // This happens outside the runtime such as Servlet filter response. Here we give a default json parser to it
+ // and keep user data not get lost.
+ return ProduceProcessorManager.INSTANCE.findDefaultProcessor();
+ }
+
+ protected String extractContentType(Response response) {
+ String contentType = response.getHeader(HttpHeaders.CONTENT_TYPE);
+ if (contentType == null) {
+ return null;
+ }
+
+ int idx = contentType.indexOf(";");
+ return idx == -1 ? contentType : contentType.substring(0, idx);
+ }
+
+ protected InvocationException createDecodeException(Invocation invocation, Response response, Exception e) {
+ RestClientTransportContext transportContext = invocation.getTransportContext();
+ HttpClientRequest httpClientRequest = transportContext.getHttpClientRequest();
+
+ LOGGER.warn(
+ "failed to decode response body, method={}, endpoint={}, uri={}, statusCode={}, reasonPhrase={}, content-type={}.",
+ httpClientRequest.method(),
+ invocation.getEndpoint().getEndpoint(),
+ httpClientRequest.absoluteURI(),
+ response.getStatusCode(),
+ response.getReasonPhrase(),
+ response.getHeader(HttpHeaders.CONTENT_TYPE));
+
+ if (response.isSuccessed()) {
+ return Exceptions.consumer(FAILED_TO_DECODE_REST_SUCCESS_RESPONSE, "failed to decode success response body.", e);
+ }
+ return Exceptions.consumer(FAILED_TO_DECODE_REST_FAIL_RESPONSE, "failed to decode fail response body.", e);
+ }
+}
diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientExceptionCodes.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientExceptionCodes.java
index 5dcef13..9c99683 100644
--- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientExceptionCodes.java
+++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/RestClientExceptionCodes.java
@@ -18,5 +18,10 @@ package org.apache.servicecomb.transport.rest.client;
public interface RestClientExceptionCodes {
String FAILED_TO_CREATE_REST_CLIENT_TRANSPORT_CONTEXT = "scb_rest_client.40000000";
+
String FAILED_TO_ENCODE_REST_CLIENT_REQUEST = "scb_rest_client.40000001";
+
+ String FAILED_TO_DECODE_REST_SUCCESS_RESPONSE = "scb_rest_client.40000002";
+
+ String FAILED_TO_DECODE_REST_FAIL_RESPONSE = "scb_rest_client.40000003";
}
diff --git a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/RestClientDecoderTest.java b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/RestClientDecoderTest.java
new file mode 100644
index 0000000..d1cd2ac
--- /dev/null
+++ b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/RestClientDecoderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.servicecomb.transport.rest.client;
+
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.catchThrowable;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.servicecomb.swagger.invocation.Response;
+import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
+import org.junit.jupiter.api.Test;
+
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.json.Json;
+
+class RestClientDecoderTest extends RestClientTestBase {
+ static RestClientDecoder decoder = new RestClientDecoder();
+
+ @Test
+ void should_decode_by_json_when_no_content_type() {
+ init("query", null, false);
+
+ Response response = Response.ok(Buffer.buffer("\"result\""));
+ decoder.decode(invocation, response);
+
+ assertThat(response.<String>getResult()).isEqualTo("result");
+ }
+
+ @Test
+ void should_decode_2xx_body() {
+ init("query", null, false);
+
+ Response response = Response.ok(Buffer.buffer("\"result\""))
+ .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+ decoder.decode(invocation, response);
+
+ assertThat(response.<String>getResult()).isEqualTo("result");
+ }
+
+ @Test
+ void should_throw_exception_when_decode_invalid_2xx_body() {
+ init("query", null, false);
+
+ Response response = Response.ok(Buffer.buffer("result"))
+ .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+ Throwable throwable = catchThrowable(() -> decoder.decode(invocation, response));
+
+ assertThat(throwable.toString()).isEqualTo(
+ "InvocationException: code=400;msg=CommonExceptionData{code='scb_rest_client.40000002', message='failed to decode success response body.', dynamic={}}");
+ }
+
+ @Test
+ void should_decode_by_content_type_with_charset() {
+ init("query", null, false);
+
+ Response response = Response.ok(Buffer.buffer("\"result\""))
+ .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON + ";charset=UTF-8");
+ decoder.decode(invocation, response);
+
+ assertThat(response.<String>getResult()).isEqualTo("result");
+ }
+
+ @Test
+ void should_throw_exception_when_decode_not_2xx_response() {
+ init("query", null, false);
+
+ CommonExceptionData data = new CommonExceptionData("error");
+ String json = Json.encodePrettily(data);
+ Response response = Response
+ .status(BAD_REQUEST)
+ .entity(Buffer.buffer(json))
+ .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+
+ Throwable throwable = catchThrowable(() -> decoder.decode(invocation, response));
+
+ assertThat(throwable.toString()).isEqualTo("InvocationException: code=400;msg={message=error}");
+ }
+
+ @Test
+ void should_throw_exception_when_decode_invalid_not_2xx_body() {
+ init("query", null, false);
+
+ Response response = Response
+ .status(BAD_REQUEST)
+ .entity(Buffer.buffer("result"))
+ .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+ Throwable throwable = catchThrowable(() -> decoder.decode(invocation, response));
+
+ assertThat(throwable.toString()).isEqualTo(
+ "InvocationException: code=400;msg=CommonExceptionData{code='scb_rest_client.40000003', message='failed to decode fail response body.', dynamic={}}");
+ }
+}
\ No newline at end of file
diff --git a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/RestClientTestBase.java b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/RestClientTestBase.java
index 7aa9535..fe2c4ba 100644
--- a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/RestClientTestBase.java
+++ b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/RestClientTestBase.java
@@ -29,7 +29,6 @@ import org.apache.servicecomb.core.Invocation;
import org.apache.servicecomb.core.SCBEngine;
import org.apache.servicecomb.core.Transport;
import org.apache.servicecomb.core.bootstrap.SCBBootstrap;
-import org.apache.servicecomb.core.definition.InvocationRuntimeType;
import org.apache.servicecomb.core.definition.OperationMeta;
import org.apache.servicecomb.core.invocation.InvocationFactory;
import org.apache.servicecomb.core.provider.consumer.ReferenceConfig;
@@ -84,7 +83,7 @@ public class RestClientTestBase {
restOperationMeta = RestMetaUtils.getRestOperationMeta(operationMeta);
invocation = InvocationFactory.forConsumer(
- referenceConfig, operationMeta, new InvocationRuntimeType(null), swaggerArgs);
+ referenceConfig, operationMeta, operationMeta.buildBaseConsumerRuntimeType(), swaggerArgs);
String url = "rest://localhost:1234?sslEnabled=" + ssl;
invocation.setEndpoint(new Endpoint(restTransport, url));