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 2018/04/23 02:26:14 UTC

[incubator-servicecomb-java-chassis] branch master updated (3ca168a -> 3b05f06)

This is an automated email from the ASF dual-hosted git repository.

liubao pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-java-chassis.git.


    from 3ca168a  [SCB-442] add springmvc archetype (#650)
     new dddbc5d  [SCB-487] prepare HttpUtils for parse download file name from http header content-disposition
     new be790e6  [SCB-487] ReadStreamPart support saveAsBytes/saveAsString/saveToFile
     new a62d7b9  [SCB-487] fix bug: avoid service center remote call during UT, which will cause random failed.
     new afb77b3  [SCB-487] add download consumer demo
     new 3b05f06  [SCB-487] fix bug: maven project name is not a valid dir name, so change from project name to artifactId

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/servicecomb/demo/TestMgr.java  |  10 +-
 .../client/CodeFirstRestTemplateSpringmvc.java     |   4 +
 ...roserviceArray.java => DownloadSchemaIntf.java} |  23 +--
 .../demo/springmvc/client/TestDownload.java        | 124 ++++++++++++++++
 .../demo/springmvc/server/DownloadSchema.java      |  91 +++++++++---
 demo/pom.xml                                       |   2 +-
 .../foundation/common/http/HttpUtils.java          |  85 +++++++++++
 .../foundation/common/http/TestHttpUtils.java      | 104 ++++++++++++++
 .../foundation/vertx/http/ReadStreamPart.java      | 103 +++++++++++++-
 .../io/vertx/core/file/impl/AsyncFileUitls.java    |  22 ++-
 .../foundation/vertx/http/TestReadStreamPart.java  | 158 ++++++++++++++++++++-
 .../registry/RemoteServiceRegistry.java            |  10 +-
 .../registry/TestServiceRegistryFactory.java       |   6 +-
 .../rest/client/http/RestClientInvocation.java     |   2 +-
 14 files changed, 688 insertions(+), 56 deletions(-)
 copy demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/{MicroserviceArray.java => DownloadSchemaIntf.java} (63%)
 create mode 100644 demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownload.java
 create mode 100644 foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/http/HttpUtils.java
 create mode 100644 foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/http/TestHttpUtils.java
 copy archetypes/business-service-jaxrs/src/main/java/org/apache/servicecomb/archetypes/HelloEndpoint.java => foundations/foundation-vertx/src/test/java/io/vertx/core/file/impl/AsyncFileUitls.java (66%)

-- 
To stop receiving notification emails like this one, please contact
liubao@apache.org.

[incubator-servicecomb-java-chassis] 05/05: [SCB-487] fix bug: maven project name is not a valid dir name, so change from project name to artifactId

Posted by li...@apache.org.
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/incubator-servicecomb-java-chassis.git

commit 3b05f0616b34aa10651d7d4730218f1831cd40e9
Author: wujimin <wu...@huawei.com>
AuthorDate: Sun Apr 22 21:38:15 2018 +0800

    [SCB-487] fix bug: maven project name is not a valid dir name, so change from project name to artifactId
---
 demo/pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/demo/pom.xml b/demo/pom.xml
index 55505d2..4724b6c 100644
--- a/demo/pom.xml
+++ b/demo/pom.xml
@@ -221,7 +221,7 @@
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-jar-plugin</artifactId>
               <configuration>
-                <outputDirectory>${demo.run.dir}/${project.name}</outputDirectory>
+                <outputDirectory>${demo.run.dir}/${project.artifactId}</outputDirectory>
                 <archive>
                   <manifest>
                     <addClasspath>true</addClasspath>

-- 
To stop receiving notification emails like this one, please contact
liubao@apache.org.

[incubator-servicecomb-java-chassis] 03/05: [SCB-487] fix bug: avoid service center remote call during UT, which will cause random failed.

Posted by li...@apache.org.
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/incubator-servicecomb-java-chassis.git

commit a62d7b968007f797cc5c877a68ddc0587137c5af
Author: wujimin <wu...@huawei.com>
AuthorDate: Sun Apr 22 13:15:14 2018 +0800

    [SCB-487] fix bug: avoid service center remote call during UT, which will cause random failed.
---
 .../serviceregistry/registry/RemoteServiceRegistry.java        | 10 +++++-----
 .../serviceregistry/registry/TestServiceRegistryFactory.java   |  6 +++++-
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/service-registry/src/main/java/org/apache/servicecomb/serviceregistry/registry/RemoteServiceRegistry.java b/service-registry/src/main/java/org/apache/servicecomb/serviceregistry/registry/RemoteServiceRegistry.java
index 2a60472..7e683d3 100644
--- a/service-registry/src/main/java/org/apache/servicecomb/serviceregistry/registry/RemoteServiceRegistry.java
+++ b/service-registry/src/main/java/org/apache/servicecomb/serviceregistry/registry/RemoteServiceRegistry.java
@@ -82,11 +82,11 @@ public class RemoteServiceRegistry extends AbstractServiceRegistry {
         serviceRegistryConfig.getHeartbeatInterval(),
         TimeUnit.SECONDS);
 
-      taskPool.scheduleAtFixedRate(
-          () -> eventBus.post(new PeriodicPullEvent()),
-          serviceRegistryConfig.getInstancePullInterval(),
-          serviceRegistryConfig.getInstancePullInterval(),
-          TimeUnit.SECONDS);
+    taskPool.scheduleAtFixedRate(
+        () -> eventBus.post(new PeriodicPullEvent()),
+        serviceRegistryConfig.getInstancePullInterval(),
+        serviceRegistryConfig.getInstancePullInterval(),
+        TimeUnit.SECONDS);
 
   }
 
diff --git a/service-registry/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestServiceRegistryFactory.java b/service-registry/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestServiceRegistryFactory.java
index 1e62441..07f7e08 100644
--- a/service-registry/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestServiceRegistryFactory.java
+++ b/service-registry/src/test/java/org/apache/servicecomb/serviceregistry/registry/TestServiceRegistryFactory.java
@@ -31,13 +31,17 @@ import org.junit.Test;
 import com.google.common.eventbus.EventBus;
 
 import mockit.Deencapsulation;
+import mockit.Mocked;
 
 /**
  * Created by   on 2017/3/31.
  */
 public class TestServiceRegistryFactory {
   @Test
-  public void testGetRemoteRegistryClient() {
+  // mock ServiceRegistryClientImpl to avoid send request to remote SC
+  // even there is no any reference to registryClient, DO NOT delete it.
+  // because what changed is class ServiceRegistryClientImpl
+  public void testGetRemoteRegistryClient(@Mocked ServiceRegistryClientImpl registryClient) {
     EventBus eventBus = new EventBus();
     ServiceRegistryConfig serviceRegistryConfig = ServiceRegistryConfig.INSTANCE;
     MicroserviceDefinition microserviceDefinition = new MicroserviceDefinition(Collections.emptyList());

-- 
To stop receiving notification emails like this one, please contact
liubao@apache.org.

[incubator-servicecomb-java-chassis] 02/05: [SCB-487] ReadStreamPart support saveAsBytes/saveAsString/saveToFile

Posted by li...@apache.org.
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/incubator-servicecomb-java-chassis.git

commit be790e6b559700faac6dc677848d586f1e0a3af0
Author: wujimin <wu...@huawei.com>
AuthorDate: Sun Apr 22 12:45:08 2018 +0800

    [SCB-487] ReadStreamPart support saveAsBytes/saveAsString/saveToFile
---
 .../foundation/vertx/http/ReadStreamPart.java      | 103 +++++++++++++-
 .../io/vertx/core/file/impl/AsyncFileUitls.java    |  28 ++++
 .../foundation/vertx/http/TestReadStreamPart.java  | 158 ++++++++++++++++++++-
 .../rest/client/http/RestClientInvocation.java     |   2 +-
 4 files changed, 285 insertions(+), 6 deletions(-)

diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/ReadStreamPart.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/ReadStreamPart.java
index 4014609..ddc56c9 100644
--- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/ReadStreamPart.java
+++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/ReadStreamPart.java
@@ -16,13 +16,26 @@
  */
 package org.apache.servicecomb.foundation.vertx.http;
 
+import java.io.File;
 import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
 
 import javax.servlet.http.Part;
+import javax.ws.rs.core.HttpHeaders;
 
+import org.apache.commons.lang.StringUtils;
+import org.apache.servicecomb.foundation.common.http.HttpUtils;
 import org.apache.servicecomb.foundation.common.part.AbstractPart;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Context;
+import io.vertx.core.Vertx;
 import io.vertx.core.buffer.Buffer;
+import io.vertx.core.file.AsyncFile;
+import io.vertx.core.file.OpenOptions;
+import io.vertx.core.http.HttpClientResponse;
 import io.vertx.core.streams.Pump;
 import io.vertx.core.streams.ReadStream;
 import io.vertx.core.streams.WriteStream;
@@ -36,9 +49,26 @@ import io.vertx.core.streams.WriteStream;
  * {@link org.apache.servicecomb.foundation.vertx.http.VertxServerResponseToHttpServletResponse#sendPart(Part) VertxServerResponseToHttpServletResponse.sendPart}
  */
 public class ReadStreamPart extends AbstractPart {
+  private static final Logger LOGGER = LoggerFactory.getLogger(ReadStreamPart.class);
+
+  private Context context;
+
   private ReadStream<Buffer> readStream;
 
-  public ReadStreamPart(ReadStream<Buffer> readStream) {
+  public ReadStreamPart(Context context, HttpClientResponse httpClientResponse) {
+    this(context, (ReadStream<Buffer>) httpClientResponse);
+
+    setSubmittedFileName(
+        HttpUtils.parseFileNameFromHeaderValue(httpClientResponse.getHeader(HttpHeaders.CONTENT_DISPOSITION)));
+
+    String contentType = httpClientResponse.getHeader(HttpHeaders.CONTENT_TYPE);
+    if (StringUtils.isNotEmpty(contentType)) {
+      this.contentType(contentType);
+    }
+  }
+
+  public ReadStreamPart(Context context, ReadStream<Buffer> readStream) {
+    this.context = context;
     this.readStream = readStream;
 
     readStream.pause();
@@ -60,4 +90,75 @@ public class ReadStreamPart extends AbstractPart {
 
     return future;
   }
+
+  public CompletableFuture<byte[]> saveAsBytes() {
+    return saveAs(buf -> {
+      return buf.getBytes();
+    });
+  }
+
+  public CompletableFuture<String> saveAsString() {
+    return saveAs(buf -> {
+      return buf.toString();
+    });
+  }
+
+  public <T> CompletableFuture<T> saveAs(Function<Buffer, T> converter) {
+    CompletableFuture<T> future = new CompletableFuture<>();
+    Buffer buffer = Buffer.buffer();
+
+    readStream.exceptionHandler(future::completeExceptionally);
+    readStream.handler(buffer::appendBuffer);
+    readStream.endHandler(v -> {
+      future.complete(converter.apply(buffer));
+    });
+    readStream.resume();
+
+    return future;
+  }
+
+  public CompletableFuture<File> saveToFile(String fileName) {
+    File file = new File(fileName);
+    file.getParentFile().mkdirs();
+    OpenOptions openOptions = new OpenOptions().setCreateNew(true);
+    return saveToFile(file, openOptions);
+  }
+
+  public CompletableFuture<File> saveToFile(File file, OpenOptions openOptions) {
+    CompletableFuture<File> future = new CompletableFuture<>();
+
+    Vertx vertx = context.owner();
+    vertx.fileSystem().open(file.getAbsolutePath(), openOptions, ar -> {
+      onFileOpened(file, ar, future);
+    });
+
+    return future;
+  }
+
+  protected void onFileOpened(File file, AsyncResult<AsyncFile> ar, CompletableFuture<File> future) {
+    if (ar.failed()) {
+      future.completeExceptionally(ar.cause());
+      return;
+    }
+
+    AsyncFile asyncFile = ar.result();
+    CompletableFuture<Void> saveFuture = saveToWriteStream(asyncFile);
+    saveFuture.whenComplete((v, saveException) -> {
+      asyncFile.close(closeAr -> {
+        if (closeAr.failed()) {
+          LOGGER.error("Failed to close file {}.", file);
+        }
+
+        // whatever close success or failed
+        // will not affect to result
+        // result just only related to write
+        if (saveException == null) {
+          future.complete(file);
+          return;
+        }
+
+        future.completeExceptionally(saveException);
+      });
+    });
+  }
 }
diff --git a/foundations/foundation-vertx/src/test/java/io/vertx/core/file/impl/AsyncFileUitls.java b/foundations/foundation-vertx/src/test/java/io/vertx/core/file/impl/AsyncFileUitls.java
new file mode 100644
index 0000000..a458f9b
--- /dev/null
+++ b/foundations/foundation-vertx/src/test/java/io/vertx/core/file/impl/AsyncFileUitls.java
@@ -0,0 +1,28 @@
+/*
+ * 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 io.vertx.core.file.impl;
+
+import io.vertx.core.file.AsyncFile;
+import io.vertx.core.file.OpenOptions;
+import io.vertx.core.impl.ContextImpl;
+import io.vertx.core.impl.VertxInternal;
+
+public class AsyncFileUitls {
+  public static AsyncFile createAsyncFile(VertxInternal vertx, String path, OpenOptions options, ContextImpl context) {
+    return new AsyncFileImpl(vertx, path, options, context);
+  }
+}
diff --git a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestReadStreamPart.java b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestReadStreamPart.java
index cffd964..0f9e730 100644
--- a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestReadStreamPart.java
+++ b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestReadStreamPart.java
@@ -17,10 +17,15 @@
 package org.apache.servicecomb.foundation.vertx.http;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 
+import javax.ws.rs.core.HttpHeaders;
+
+import org.apache.commons.io.FileUtils;
 import org.apache.servicecomb.foundation.vertx.stream.InputStreamToReadStream;
 import org.hamcrest.Matchers;
 import org.junit.Assert;
@@ -30,18 +35,35 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
 import io.vertx.core.AsyncResult;
+import io.vertx.core.Context;
 import io.vertx.core.Future;
 import io.vertx.core.Handler;
 import io.vertx.core.Vertx;
 import io.vertx.core.buffer.Buffer;
+import io.vertx.core.file.AsyncFile;
+import io.vertx.core.file.FileSystem;
+import io.vertx.core.file.FileSystemException;
+import io.vertx.core.file.OpenOptions;
+import io.vertx.core.file.impl.AsyncFileUitls;
+import io.vertx.core.file.impl.FileSystemImpl;
+import io.vertx.core.file.impl.WindowsFileSystem;
+import io.vertx.core.http.HttpClientResponse;
+import io.vertx.core.impl.ContextImpl;
+import io.vertx.core.impl.EventLoopContext;
+import io.vertx.core.impl.Utils;
+import io.vertx.core.impl.VertxInternal;
 import io.vertx.core.streams.WriteStream;
+import mockit.Expectations;
 import mockit.Mock;
 import mockit.MockUp;
 import mockit.Mocked;
 
 public class TestReadStreamPart {
   @Mocked
-  Vertx vertx;
+  VertxInternal vertx;
+
+  //  @Mocked
+  ContextImpl context;
 
   String src = "src";
 
@@ -54,14 +76,31 @@ public class TestReadStreamPart {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
+  FileSystem fileSystem;
+
+  protected FileSystem getFileSystem() {
+    return Utils.isWindows() ? new WindowsFileSystem(vertx) : new FileSystemImpl(vertx);
+  }
 
   @Before
   public void setup() {
-    readStream = new InputStreamToReadStream(vertx, inputStream);
-    part = new ReadStreamPart(readStream);
-
     new MockUp<Vertx>(vertx) {
       @Mock
+      FileSystem fileSystem() {
+        return fileSystem;
+      }
+
+      @Mock
+      ContextImpl getContext() {
+        return context;
+      }
+
+      @Mock
+      ContextImpl getOrCreateContext() {
+        return context;
+      }
+
+      @Mock
       <T> void executeBlocking(Handler<Future<T>> blockingCodeHandler, boolean ordered,
           Handler<AsyncResult<T>> resultHandler) {
         Future<T> future = Future.future();
@@ -69,6 +108,78 @@ public class TestReadStreamPart {
         future.setHandler(resultHandler);
       }
     };
+
+    context = new EventLoopContext(vertx, null, null, null, "id", null, null);
+    new MockUp<Context>(context) {
+      @Mock
+      Vertx owner() {
+        return vertx;
+      }
+
+      @Mock
+      void runOnContext(Handler<Void> task) {
+        task.handle(null);
+      }
+
+      @Mock
+      <T> void executeBlocking(Handler<Future<T>> blockingCodeHandler, Handler<AsyncResult<T>> resultHandler) {
+        Future<T> future = Future.future();
+        blockingCodeHandler.handle(future);
+        future.setHandler(resultHandler);
+      }
+    };
+
+    fileSystem = getFileSystem();
+
+    readStream = new InputStreamToReadStream(vertx, inputStream);
+    part = new ReadStreamPart(context, readStream);
+
+    new MockUp<FileSystem>(fileSystem) {
+      @Mock
+      FileSystem open(String path, OpenOptions options, Handler<AsyncResult<AsyncFile>> handler) {
+        try {
+          AsyncFile asyncFile = AsyncFileUitls.createAsyncFile(vertx, path, options, context);
+          handler.handle(Future.succeededFuture(asyncFile));
+        } catch (Exception e) {
+          handler.handle(Future.failedFuture(e));
+        }
+        return fileSystem;
+      }
+    };
+  }
+
+  @Test
+  public void constructFromHttpClientResponse_noContentType(@Mocked HttpClientResponse httpClientResponse) {
+    new Expectations() {
+      {
+        httpClientResponse.getHeader(HttpHeaders.CONTENT_DISPOSITION);
+        result = "xx;filename=name.txt";
+        httpClientResponse.getHeader(HttpHeaders.CONTENT_TYPE);
+        result = null;
+      }
+    };
+
+    part = new ReadStreamPart(context, httpClientResponse);
+
+    Assert.assertEquals("name.txt", part.getSubmittedFileName());
+    Assert.assertEquals("text/plain", part.getContentType());
+  }
+
+  @Test
+  public void constructFromHttpClientResponse_hasContentType(@Mocked HttpClientResponse httpClientResponse) {
+    new Expectations() {
+      {
+        httpClientResponse.getHeader(HttpHeaders.CONTENT_DISPOSITION);
+        result = "xx;filename=name.txt";
+        httpClientResponse.getHeader(HttpHeaders.CONTENT_TYPE);
+        result = "type";
+      }
+    };
+
+    part = new ReadStreamPart(context, httpClientResponse);
+
+    Assert.assertEquals("name.txt", part.getSubmittedFileName());
+    Assert.assertEquals("type", part.getContentType());
   }
 
   @Test
@@ -128,4 +239,43 @@ public class TestReadStreamPart {
 
     part.saveToWriteStream(writeStream).get();
   }
+
+  @Test
+  public void saveAsBytes() throws InterruptedException, ExecutionException {
+    Assert.assertArrayEquals(src.getBytes(), part.saveAsBytes().get());
+  }
+
+  @Test
+  public void saveAsString() throws InterruptedException, ExecutionException {
+    Assert.assertEquals(src, part.saveAsString().get());
+  }
+
+  @Test
+  public void saveToFile() throws InterruptedException, ExecutionException, IOException {
+    File dir = new File("target/notExist-" + UUID.randomUUID().toString());
+    File file = new File(dir, "a.txt");
+
+    Assert.assertFalse(dir.exists());
+
+    part.saveToFile(file.getAbsolutePath()).get();
+
+    Assert.assertEquals(src, FileUtils.readFileToString(file));
+
+    FileUtils.forceDelete(dir);
+    Assert.assertFalse(dir.exists());
+  }
+
+  @Test
+  public void saveToFile_notExist_notCreate() throws InterruptedException, ExecutionException, IOException {
+    File dir = new File("target/notExist-" + UUID.randomUUID().toString());
+    File file = new File(dir, "a.txt");
+
+    Assert.assertFalse(dir.exists());
+
+    expectedException.expect(ExecutionException.class);
+    expectedException.expectCause(Matchers.instanceOf(FileSystemException.class));
+
+    OpenOptions openOptions = new OpenOptions().setCreateNew(false);
+    part.saveToFile(file, openOptions).get();
+  }
 }
diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java
index 9a586a1..db148dd 100644
--- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java
+++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/RestClientInvocation.java
@@ -155,7 +155,7 @@ public class RestClientInvocation {
 
     if (HttpStatus.isSuccess(clientResponse.statusCode())
         && Part.class.equals(invocation.getOperationMeta().getMethod().getReturnType())) {
-      ReadStreamPart part = new ReadStreamPart(httpClientResponse);
+      ReadStreamPart part = new ReadStreamPart(httpClientWithContext.context(), httpClientResponse);
       invocation.getHandlerContext().put(RestConst.READ_STREAM_PART, part);
       processResponseBody(null);
       return;

-- 
To stop receiving notification emails like this one, please contact
liubao@apache.org.

[incubator-servicecomb-java-chassis] 01/05: [SCB-487] prepare HttpUtils for parse download file name from http header content-disposition

Posted by li...@apache.org.
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/incubator-servicecomb-java-chassis.git

commit dddbc5da7553494534ab7640ed67475541db151d
Author: wujimin <wu...@huawei.com>
AuthorDate: Sun Apr 22 11:02:35 2018 +0800

    [SCB-487] prepare HttpUtils for parse download file name from http header content-disposition
---
 .../foundation/common/http/HttpUtils.java          |  85 +++++++++++++++++
 .../foundation/common/http/TestHttpUtils.java      | 104 +++++++++++++++++++++
 2 files changed, 189 insertions(+)

diff --git a/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/http/HttpUtils.java b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/http/HttpUtils.java
new file mode 100644
index 0000000..deb6a97
--- /dev/null
+++ b/foundations/foundation-common/src/main/java/org/apache/servicecomb/foundation/common/http/HttpUtils.java
@@ -0,0 +1,85 @@
+/*
+ * 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.foundation.common.http;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import org.springframework.util.StringUtils;
+
+public final class HttpUtils {
+  private HttpUtils() {
+  }
+
+  /**
+   * paramName is not case sensitive
+   * @param headerValue example: attachment;filename=a.txt
+   * 
+   */
+  // 
+  public static String parseParamFromHeaderValue(String headerValue, String paramName) {
+    if (StringUtils.isEmpty(headerValue)) {
+      return null;
+    }
+
+    for (String value : headerValue.split(";")) {
+      int idx = value.indexOf('=');
+      if (idx == -1) {
+        continue;
+      }
+
+      if (paramName.equalsIgnoreCase(value.substring(0, idx))) {
+        return value.substring(idx + 1);
+      }
+    }
+    return null;
+  }
+
+  public static String uriEncode(String value) {
+    return uriEncode(value, StandardCharsets.UTF_8.name());
+  }
+
+  public static String uriEncode(String value, String enc) {
+    try {
+      return URLEncoder.encode(value, enc).replace("+", "%20");
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalStateException(String.format("uriEncode failed, value=\"%s\", enc=\"%s\".", value, enc), e);
+    }
+  }
+
+  public static String uriDecode(String value) {
+    return uriDecode(value, StandardCharsets.UTF_8.name());
+  }
+
+  public static String uriDecode(String value, String enc) {
+    try {
+      return URLDecoder.decode(value, enc);
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalStateException(String.format("uriDecode failed, value=\"%s\", enc=\"%s\".", value, enc), e);
+    }
+  }
+
+  public static String parseFileNameFromHeaderValue(String headerValue) {
+    String fileName = parseParamFromHeaderValue(headerValue, "filename");
+    fileName = StringUtils.isEmpty(fileName) ? "default" : fileName;
+    fileName = uriDecode(fileName);
+    return new File(fileName).getName();
+  }
+}
diff --git a/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/http/TestHttpUtils.java b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/http/TestHttpUtils.java
new file mode 100644
index 0000000..18f8906
--- /dev/null
+++ b/foundations/foundation-common/src/test/java/org/apache/servicecomb/foundation/common/http/TestHttpUtils.java
@@ -0,0 +1,104 @@
+/*
+ * 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.foundation.common.http;
+
+import java.io.UnsupportedEncodingException;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+
+public class TestHttpUtils {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void parseParamFromHeaderValue_normal() {
+    Assert.assertEquals("v", HttpUtils.parseParamFromHeaderValue("xx;k=v", "k"));
+  }
+
+  @Test
+  public void parseParamFromHeaderValue_normal_ignoreCase() {
+    Assert.assertEquals("v", HttpUtils.parseParamFromHeaderValue("xx;K=v", "k"));
+  }
+
+  @Test
+  public void parseParamFromHeaderValue_null() {
+    Assert.assertNull(HttpUtils.parseParamFromHeaderValue(null, "k"));
+  }
+
+  @Test
+  public void parseParamFromHeaderValue_noKv() {
+    Assert.assertNull(HttpUtils.parseParamFromHeaderValue("xx", "k"));
+  }
+
+  @Test
+  public void parseParamFromHeaderValue_noV() {
+    Assert.assertEquals("", HttpUtils.parseParamFromHeaderValue("xx;k=", "k"));
+  }
+
+  @Test
+  public void parseParamFromHeaderValue_keyNotFound() {
+    Assert.assertNull(HttpUtils.parseParamFromHeaderValue("xx;k=", "kk"));
+  }
+
+  @Test
+  public void uriEncode_chineseAndSpace() {
+    String encoded = HttpUtils.uriEncode("测 试");
+    Assert.assertEquals("%E6%B5%8B%20%E8%AF%95", encoded);
+    Assert.assertEquals("测 试", HttpUtils.uriDecode(encoded));
+  }
+
+  @Test
+  public void uriEncode_failed() {
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage(Matchers.is("uriEncode failed, value=\"\", enc=\"notExistEnc\"."));
+    expectedException.expectCause(Matchers.instanceOf(UnsupportedEncodingException.class));
+
+    HttpUtils.uriEncode("", "notExistEnc");
+  }
+
+  @Test
+  public void uriDecode_failed() {
+    expectedException.expect(IllegalStateException.class);
+    expectedException
+        .expectMessage(Matchers.is("uriDecode failed, value=\"%E6%B5%8B%20%E8%AF%95\", enc=\"notExistEnc\"."));
+    expectedException.expectCause(Matchers.instanceOf(UnsupportedEncodingException.class));
+
+    HttpUtils.uriDecode("%E6%B5%8B%20%E8%AF%95", "notExistEnc");
+  }
+
+  @Test
+  public void parseFileNameFromHeaderValue() {
+    String fileName = "测 试.txt";
+    String encoded = HttpUtils.uriEncode(fileName);
+    Assert.assertEquals(fileName, HttpUtils.parseFileNameFromHeaderValue("xx;filename=" + encoded));
+  }
+
+  @Test
+  public void parseFileNameFromHeaderValue_defaultName() {
+    Assert.assertEquals("default", HttpUtils.parseFileNameFromHeaderValue("xx"));
+  }
+
+  @Test
+  public void parseFileNameFromHeaderValue_ignorePath() {
+    Assert.assertEquals("a.txt", HttpUtils.parseFileNameFromHeaderValue("xx;filename=../../a.txt"));
+  }
+}

-- 
To stop receiving notification emails like this one, please contact
liubao@apache.org.

[incubator-servicecomb-java-chassis] 04/05: [SCB-487] add download consumer demo

Posted by li...@apache.org.
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/incubator-servicecomb-java-chassis.git

commit afb77b3110bb7508c27f40408ecab59b12be0d63
Author: wujimin <wu...@huawei.com>
AuthorDate: Sun Apr 22 17:59:56 2018 +0800

    [SCB-487] add download consumer demo
---
 .../java/org/apache/servicecomb/demo/TestMgr.java  |  10 +-
 .../client/CodeFirstRestTemplateSpringmvc.java     |   4 +
 .../demo/springmvc/client/DownloadSchemaIntf.java  |  37 ++++++
 .../demo/springmvc/client/TestDownload.java        | 124 +++++++++++++++++++++
 .../demo/springmvc/server/DownloadSchema.java      |  91 ++++++++++++---
 5 files changed, 247 insertions(+), 19 deletions(-)

diff --git a/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/TestMgr.java b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/TestMgr.java
index c72bb36..b0086f6 100644
--- a/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/TestMgr.java
+++ b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/TestMgr.java
@@ -40,6 +40,10 @@ public class TestMgr {
   }
 
   public static void check(Object expect, Object real) {
+    check(expect, real, null);
+  }
+
+  public static void check(Object expect, Object real, Throwable error) {
     if (expect == real) {
       return;
     }
@@ -48,7 +52,11 @@ public class TestMgr {
     String strReal = String.valueOf(real);
 
     if (!strExpect.equals(strReal)) {
-      errorList.add(new Error(msg + " | Expect " + strExpect + ", but " + strReal));
+      Error newError = new Error(msg + " | Expect " + strExpect + ", but " + strReal);
+      if (error != null) {
+        newError.setStackTrace(error.getStackTrace());
+      }
+      errorList.add(newError);
     }
   }
 
diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstRestTemplateSpringmvc.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstRestTemplateSpringmvc.java
index f23e882..2303cc0 100644
--- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstRestTemplateSpringmvc.java
+++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstRestTemplateSpringmvc.java
@@ -70,8 +70,12 @@ public class CodeFirstRestTemplateSpringmvc extends CodeFirstRestTemplate {
 
   private TestGeneric testGeneric = new TestGeneric();
 
+  private TestDownload testDownload = new TestDownload();
+
   @Override
   protected void testOnlyRest(RestTemplate template, String cseUrlPrefix) {
+    testDownload.runRest();
+
     try {
       testUpload(template, cseUrlPrefix);
     } catch (IOException e) {
diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/DownloadSchemaIntf.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/DownloadSchemaIntf.java
new file mode 100644
index 0000000..48b9a0f
--- /dev/null
+++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/DownloadSchemaIntf.java
@@ -0,0 +1,37 @@
+/*
+ * 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.demo.springmvc.client;
+
+import org.apache.servicecomb.foundation.vertx.http.ReadStreamPart;
+
+public interface DownloadSchemaIntf {
+  ReadStreamPart tempFileEntity(String content);
+
+  ReadStreamPart tempFilePart(String content);
+
+  ReadStreamPart file(String content);
+
+  ReadStreamPart chineseAndSpaceFile(String content);
+
+  ReadStreamPart resource(String content);
+
+  ReadStreamPart entityResource(String content);
+
+  ReadStreamPart entityInputStream(String content);
+
+  ReadStreamPart netInputStream(String content);
+}
diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownload.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownload.java
new file mode 100644
index 0000000..303c001
--- /dev/null
+++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestDownload.java
@@ -0,0 +1,124 @@
+/*
+ * 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.demo.springmvc.client;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.servicecomb.demo.TestMgr;
+import org.apache.servicecomb.foundation.vertx.http.ReadStreamPart;
+import org.apache.servicecomb.provider.pojo.Invoker;
+import org.apache.servicecomb.provider.springmvc.reference.CseRestTemplate;
+import org.springframework.web.client.RestTemplate;
+
+public class TestDownload {
+  private File dir = new File("target/download");
+
+  private DownloadSchemaIntf intf = Invoker.createProxy("springmvc", "download", DownloadSchemaIntf.class);
+
+  private RestTemplate restTemplate = new CseRestTemplate();
+
+  private String prefix = "cse://springmvc/download";
+
+  private List<CompletableFuture<?>> futures = new ArrayList<>();
+
+  private String content = "file content";
+
+  public TestDownload() {
+    FileUtils.deleteQuietly(dir);
+  }
+
+  private String readFileToString(File file) {
+    try {
+      return FileUtils.readFileToString(file);
+    } catch (IOException e) {
+      return "read file failed:" + e.getMessage();
+    }
+  }
+
+  private CompletableFuture<File> checkFile(ReadStreamPart part) {
+    CompletableFuture<File> future = part.saveToFile("target/download/"
+        + UUID.randomUUID().toString()
+        + "-"
+        + part.getSubmittedFileName());
+    return checkFuture(future);
+  }
+
+  private <T> CompletableFuture<T> checkFuture(CompletableFuture<T> future) {
+    Error error = new Error();
+    future.whenComplete((result, e) -> {
+      Object value = result;
+      if (File.class.isInstance(value)) {
+        value = readFileToString((File) value);
+        ((File) value).delete();
+      } else if (byte[].class.isInstance(value)) {
+        value = new String((byte[]) value);
+      }
+
+      TestMgr.check(content, value, error);
+    });
+
+    return future;
+  }
+
+  private ReadStreamPart templateGet(String methodPath) {
+    return restTemplate
+        .getForObject(prefix + "/" + methodPath + "?content={content}",
+            ReadStreamPart.class,
+            content);
+  }
+
+  public void runRest() {
+    futures.add(checkFile(intf.tempFileEntity(content)));
+    futures.add(checkFuture(templateGet("tempFileEntity").saveAsBytes()));
+
+    futures.add(checkFile(intf.tempFilePart(content)));
+    futures.add(checkFuture(templateGet("tempFilePart").saveAsString()));
+
+    futures.add(checkFile(intf.file(content)));
+    futures.add(checkFuture(templateGet("file").saveAsString()));
+
+    // fix in next PR
+    //    futures.add(checkFile(intf.chineseAndSpaceFile(content)));
+    //    futures.add(checkFuture(templateGet("chineseAndSpaceFile").saveAsString()));
+
+    futures.add(checkFile(intf.resource(content)));
+    futures.add(checkFuture(templateGet("resource").saveAsString()));
+
+    futures.add(checkFile(intf.entityResource(content)));
+    futures.add(checkFuture(templateGet("entityResource").saveAsString()));
+
+    futures.add(checkFile(intf.entityInputStream(content)));
+    futures.add(checkFuture(templateGet("entityInputStream").saveAsString()));
+
+    futures.add(checkFile(intf.netInputStream(content)));
+    futures.add(checkFuture(templateGet("netInputStream").saveAsString()));
+
+    try {
+      CompletableFuture
+          .allOf(futures.toArray(new CompletableFuture[futures.size()]))
+          .get();
+    } catch (InterruptedException | ExecutionException e1) {
+    }
+  }
+}
diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java
index 7125511..c6124d2 100644
--- a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java
+++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/DownloadSchema.java
@@ -20,17 +20,26 @@ import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.UUID;
 
 import javax.servlet.http.Part;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.bootstrap.HttpServer;
+import org.apache.http.impl.bootstrap.ServerBootstrap;
 import org.apache.servicecomb.foundation.common.part.FilePart;
 import org.apache.servicecomb.provider.rest.common.RestSchema;
 import org.springframework.core.io.ByteArrayResource;
 import org.springframework.core.io.Resource;
 import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -41,24 +50,45 @@ import io.swagger.annotations.ApiResponses;
 @RestSchema(schemaId = "download")
 @RequestMapping(path = "/download")
 public class DownloadSchema {
-  File tempDir = new File("downloadTemp");
+  File tempDir = new File("target/downloadTemp");
 
   public DownloadSchema() throws IOException {
+    FileUtils.deleteQuietly(tempDir);
     FileUtils.forceMkdir(tempDir);
+
+    // for download from net stream case
+    HttpServer server = ServerBootstrap
+        .bootstrap()
+        .setListenerPort(9000)
+        .registerHandler("/download/netInputStream", (req, resp, context) -> {
+          String uri = req.getRequestLine().getUri();
+          String query = URI.create(uri).getQuery();
+          int idx = query.indexOf('=');
+          String content = query.substring(idx + 1);
+          content = URLDecoder.decode(content, StandardCharsets.UTF_8.name());
+          resp.setEntity(new StringEntity(content, StandardCharsets.UTF_8.name()));
+        })
+        .create();
+    server.start();
+  }
+
+  protected File createTempFile(String content) throws IOException {
+    return createTempFile(null, content);
   }
 
-  // content is file name
-  protected File createTempFile() throws IOException {
-    String name = "download-" + UUID.randomUUID().toString() + ".txt";
+  protected File createTempFile(String name, String content) throws IOException {
+    if (name == null) {
+      name = "download-" + UUID.randomUUID().toString() + ".txt";
+    }
     File file = new File(tempDir, name);
-    FileUtils.write(file, name);
+    FileUtils.write(file, content);
     return file;
   }
 
   // customize HttpHeaders.CONTENT_DISPOSITION to be "attachment;filename=tempFileEntity.txt"
   @GetMapping(path = "/tempFileEntity")
-  public ResponseEntity<Part> downloadTempFileEntity() throws IOException {
-    File file = createTempFile();
+  public ResponseEntity<Part> tempFileEntity(String content) throws IOException {
+    File file = createTempFile(content);
 
     return ResponseEntity
         .ok()
@@ -69,8 +99,8 @@ public class DownloadSchema {
 
   // generate HttpHeaders.CONTENT_DISPOSITION to be "attachment;filename=tempFilePart.txt" automatically
   @GetMapping(path = "/tempFilePart")
-  public Part downloadTempFilePart() throws IOException {
-    File file = createTempFile();
+  public Part tempFilePart(String content) throws IOException {
+    File file = createTempFile(content);
 
     return new FilePart(null, file)
         .setDeleteAfterFinished(true)
@@ -78,19 +108,27 @@ public class DownloadSchema {
   }
 
   @GetMapping(path = "/file")
-  public File downloadFile() throws IOException {
-    return new File(this.getClass().getClassLoader().getResource("microservice.yaml").getFile());
+  public File file(String content) throws IOException {
+    return createTempFile("file.txt", content);
+  }
+
+  @GetMapping(path = "/chineseAndSpaceFile")
+  public Part chineseAndSpaceFile(String content) throws IOException {
+    File file = createTempFile(content);
+    return new FilePart(null, file)
+        .setDeleteAfterFinished(true)
+        .setSubmittedFileName("测 试.test.txt");
   }
 
   @GetMapping(path = "/resource")
   @ApiResponses({
       @ApiResponse(code = 200, response = File.class, message = ""),
   })
-  public Resource downloadResource() throws IOException {
-    return new ByteArrayResource("abc".getBytes(StandardCharsets.UTF_8)) {
+  public Resource resource(String content) throws IOException {
+    return new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)) {
       @Override
       public String getFilename() {
-        return "abc.txt";
+        return "resource.txt";
       }
     };
   }
@@ -99,21 +137,38 @@ public class DownloadSchema {
   @ApiResponses({
       @ApiResponse(code = 200, response = File.class, message = ""),
   })
-  public ResponseEntity<Resource> downloadEntityResource() throws IOException {
+  public ResponseEntity<Resource> entityResource(String content) throws IOException {
     return ResponseEntity
         .ok()
+        .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
         .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=entityResource.txt")
-        .body(new ByteArrayResource("entityResource".getBytes(StandardCharsets.UTF_8)));
+        .body(new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)));
   }
 
   @GetMapping(path = "/entityInputStream")
   @ApiResponses({
       @ApiResponse(code = 200, response = File.class, message = ""),
   })
-  public ResponseEntity<InputStream> downloadEntityInputStream() throws IOException {
+  public ResponseEntity<InputStream> entityInputStream(String content) throws IOException {
     return ResponseEntity
         .ok()
+        .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
         .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=entityInputStream.txt")
-        .body(new ByteArrayInputStream("entityInputStream".getBytes(StandardCharsets.UTF_8)));
+        .body(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
+  }
+
+  @GetMapping(path = "/netInputStream")
+  @ApiResponses({
+      @ApiResponse(code = 200, response = File.class, message = ""),
+  })
+  public ResponseEntity<InputStream> netInputStream(String content) throws IOException {
+    URL url = new URL("http://localhost:9000/download/netInputStream?content="
+        + URLEncoder.encode(content, StandardCharsets.UTF_8.name()));
+    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+    return ResponseEntity
+        .ok()
+        .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
+        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=netInputStream.txt")
+        .body(conn.getInputStream());
   }
 }

-- 
To stop receiving notification emails like this one, please contact
liubao@apache.org.