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));