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/20 01:50:27 UTC

[incubator-servicecomb-java-chassis] 03/09: SCB-483 HttpServletResponseEx from vertx support sendPart

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 211afe6cc8815c08e6d8d452c09e8c75b4d08bf4
Author: wujimin <wu...@huawei.com>
AuthorDate: Tue Apr 17 23:50:39 2018 +0800

    SCB-483 HttpServletResponseEx from vertx support sendPart
---
 .../vertx/http/AbstractHttpServletResponse.java    |   7 ++
 .../vertx/http/HttpServletResponseEx.java          |   5 +
 .../vertx/http/StandardHttpServletResponseEx.java  |   7 ++
 .../VertxServerResponseToHttpServletResponse.java  |  52 ++++++++
 .../http/TestAbstractHttpServletResponse.java      |  15 +++
 .../http/TestStandardHttpServletResponseEx.java    |  24 ++++
 ...stVertxServerResponseToHttpServletResponse.java | 132 +++++++++++++++++++++
 7 files changed, 242 insertions(+)

diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/AbstractHttpServletResponse.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/AbstractHttpServletResponse.java
index a09e773..2140429 100644
--- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/AbstractHttpServletResponse.java
+++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/AbstractHttpServletResponse.java
@@ -23,9 +23,11 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.Cookie;
+import javax.servlet.http.Part;
 import javax.ws.rs.core.Response.StatusType;
 
 public abstract class AbstractHttpServletResponse extends BodyBufferSupportImpl implements HttpServletResponseEx {
@@ -230,4 +232,9 @@ public abstract class AbstractHttpServletResponse extends BodyBufferSupportImpl
   public Object getAttribute(String key) {
     return this.attributes.get(key);
   }
+
+  @Override
+  public CompletableFuture<Void> sendPart(Part body) {
+    throw new Error("not supported method");
+  }
 }
diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/HttpServletResponseEx.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/HttpServletResponseEx.java
index 9749f4b..a1ee34e 100644
--- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/HttpServletResponseEx.java
+++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/HttpServletResponseEx.java
@@ -17,7 +17,10 @@
 
 package org.apache.servicecomb.foundation.vertx.http;
 
+import java.util.concurrent.CompletableFuture;
+
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
 import javax.ws.rs.core.Response.StatusType;
 
 public interface HttpServletResponseEx extends HttpServletResponse, BodyBufferSupport {
@@ -26,4 +29,6 @@ public interface HttpServletResponseEx extends HttpServletResponse, BodyBufferSu
   void setAttribute(String key, Object value);
 
   Object getAttribute(String key);
+
+  CompletableFuture<Void> sendPart(Part body);
 }
diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/StandardHttpServletResponseEx.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/StandardHttpServletResponseEx.java
index c2c1408..13c0ba7 100644
--- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/StandardHttpServletResponseEx.java
+++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/StandardHttpServletResponseEx.java
@@ -20,9 +20,11 @@ package org.apache.servicecomb.foundation.vertx.http;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.Part;
 import javax.ws.rs.core.Response.StatusType;
 
 import org.apache.servicecomb.foundation.common.http.HttpStatus;
@@ -95,4 +97,9 @@ public class StandardHttpServletResponseEx extends HttpServletResponseWrapper im
   public Object getAttribute(String key) {
     return this.attributes.get(key);
   }
+
+  @Override
+  public CompletableFuture<Void> sendPart(Part body) {
+    throw new Error("not supported method");
+  }
 }
diff --git a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerResponseToHttpServletResponse.java b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerResponseToHttpServletResponse.java
index ca5510c..9f5db55 100644
--- a/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerResponseToHttpServletResponse.java
+++ b/foundations/foundation-vertx/src/main/java/org/apache/servicecomb/foundation/vertx/http/VertxServerResponseToHttpServletResponse.java
@@ -18,17 +18,23 @@
 package org.apache.servicecomb.foundation.vertx.http;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Collection;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 
+import javax.servlet.http.Part;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response.StatusType;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.servicecomb.foundation.common.http.HttpStatus;
+import org.apache.servicecomb.foundation.vertx.stream.InputStreamToReadStream;
 
 import io.vertx.core.Context;
 import io.vertx.core.Vertx;
 import io.vertx.core.http.HttpServerResponse;
+import io.vertx.core.streams.Pump;
 
 public class VertxServerResponseToHttpServletResponse extends AbstractHttpServletResponse {
   private Context context;
@@ -118,4 +124,50 @@ public class VertxServerResponseToHttpServletResponse extends AbstractHttpServle
 
     serverResponse.end(bodyBuffer);
   }
+
+  @Override
+  public CompletableFuture<Void> sendPart(Part part) {
+    CompletableFuture<Void> future = new CompletableFuture<Void>();
+
+    prepareSendPartHeader(part);
+
+    try {
+      InputStream is = part.getInputStream();
+      context.runOnContext(v -> {
+        InputStreamToReadStream aa = new InputStreamToReadStream(context.owner(), is);
+        aa.exceptionHandler(t -> {
+          clearPartResource(part, is);
+          future.completeExceptionally(t);
+        });
+        aa.endHandler(V -> {
+          clearPartResource(part, is);
+          future.complete(null);
+        });
+        Pump.pump(aa, serverResponse).start();
+      });
+    } catch (IOException e) {
+      future.completeExceptionally(e);
+    }
+
+    return future;
+  }
+
+  protected void prepareSendPartHeader(Part part) {
+    if (!serverResponse.headers().contains(HttpHeaders.CONTENT_LENGTH)) {
+      serverResponse.setChunked(true);
+    }
+
+    if (!serverResponse.headers().contains(HttpHeaders.CONTENT_TYPE)) {
+      serverResponse.putHeader(HttpHeaders.CONTENT_TYPE, part.getContentType());
+    }
+
+    if (!serverResponse.headers().contains(HttpHeaders.CONTENT_DISPOSITION)) {
+      serverResponse.putHeader(HttpHeaders.CONTENT_DISPOSITION,
+          "attachment;filename=" + part.getSubmittedFileName());
+    }
+  }
+
+  protected void clearPartResource(Part part, InputStream is) {
+    IOUtils.closeQuietly(is);
+  }
 }
diff --git a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestAbstractHttpServletResponse.java b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestAbstractHttpServletResponse.java
index 2462772..ec884ee 100644
--- a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestAbstractHttpServletResponse.java
+++ b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestAbstractHttpServletResponse.java
@@ -20,10 +20,12 @@ package org.apache.servicecomb.foundation.vertx.http;
 import java.io.IOException;
 
 import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
+
 public class TestAbstractHttpServletResponse {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
@@ -304,4 +306,17 @@ public class TestAbstractHttpServletResponse {
 
     response.getStatusType();
   }
+
+  @Test
+  public void attribute() {
+    response.setAttribute("k", "v");
+    Assert.assertEquals("v", response.getAttribute("k"));
+  }
+
+  @Test
+  public void sendPart() {
+    setExceptionExpected();
+
+    response.sendPart(null);
+  }
 }
diff --git a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestStandardHttpServletResponseEx.java b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestStandardHttpServletResponseEx.java
index 82853b2..00cb71e 100644
--- a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestStandardHttpServletResponseEx.java
+++ b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestStandardHttpServletResponseEx.java
@@ -23,9 +23,12 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.WriteListener;
 import javax.servlet.http.HttpServletResponse;
 
+import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import io.vertx.core.buffer.Buffer;
 import mockit.Mock;
@@ -38,6 +41,14 @@ public class TestStandardHttpServletResponseEx {
 
   StandardHttpServletResponseEx responseEx;
 
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private void setExceptionExpected() {
+    expectedException.expect(Error.class);
+    expectedException.expectMessage(Matchers.is("not supported method"));
+  }
+
   @Before
   public void setup() {
     responseEx = new StandardHttpServletResponseEx(response);
@@ -114,4 +125,17 @@ public class TestStandardHttpServletResponseEx {
     responseEx.flushBuffer();
     Assert.assertEquals("body", buffer.toString());
   }
+
+  @Test
+  public void attribute() {
+    responseEx.setAttribute("k", "v");
+    Assert.assertEquals("v", responseEx.getAttribute("k"));
+  }
+
+  @Test
+  public void sendPart() {
+    setExceptionExpected();
+
+    responseEx.sendPart(null);
+  }
 }
diff --git a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestVertxServerResponseToHttpServletResponse.java b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestVertxServerResponseToHttpServletResponse.java
index d821d14..c69d41c 100644
--- a/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestVertxServerResponseToHttpServletResponse.java
+++ b/foundations/foundation-vertx/src/test/java/org/apache/servicecomb/foundation/vertx/http/TestVertxServerResponseToHttpServletResponse.java
@@ -18,7 +18,11 @@
 package org.apache.servicecomb.foundation.vertx.http;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 
+import javax.servlet.http.Part;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response.StatusType;
 
@@ -30,9 +34,12 @@ import org.junit.Rule;
 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.MultiMap;
+import io.vertx.core.Vertx;
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.http.HttpServerResponse;
 import io.vertx.core.impl.VertxImpl;
@@ -56,11 +63,16 @@ public class TestVertxServerResponseToHttpServletResponse {
   boolean runOnContextInvoked;
 
   @Mocked
+  Vertx vertx;
+
+  @Mocked
   Context context;
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
+  boolean chunked;
+
   @Before
   public void setup() {
     serverResponse = new MockUp<HttpServerResponse>() {
@@ -92,6 +104,12 @@ public class TestVertxServerResponseToHttpServletResponse {
       }
 
       @Mock
+      HttpServerResponse putHeader(String name, String value) {
+        headers.set(name, value);
+        return serverResponse;
+      }
+
+      @Mock
       void end() {
         flushWithBody = false;
       }
@@ -100,6 +118,17 @@ public class TestVertxServerResponseToHttpServletResponse {
       void end(Buffer chunk) {
         flushWithBody = true;
       }
+
+      @Mock
+      HttpServerResponse setChunked(boolean chunked) {
+        TestVertxServerResponseToHttpServletResponse.this.chunked = chunked;
+        return serverResponse;
+      }
+
+      @Mock
+      boolean isChunked() {
+        return chunked;
+      }
     }.getMockInstance();
 
     new Expectations(VertxImpl.class) {
@@ -115,6 +144,22 @@ public class TestVertxServerResponseToHttpServletResponse {
         runOnContextInvoked = true;
         action.handle(null);
       }
+
+      @Mock
+      Vertx owner() {
+        return vertx;
+      }
+    };
+
+    new MockUp<Vertx>(vertx) {
+      @Mock
+      <T> void executeBlocking(Handler<Future<T>> blockingCodeHandler, boolean ordered,
+          Handler<AsyncResult<T>> resultHandler) {
+        Future<T> future = Future.future();
+        future.setHandler(resultHandler);
+
+        blockingCodeHandler.handle(future);
+      }
     };
 
     response = new VertxServerResponseToHttpServletResponse(serverResponse);
@@ -246,4 +291,91 @@ public class TestVertxServerResponseToHttpServletResponse {
 
     Assert.assertTrue(flushWithBody);
   }
+
+  @Test
+  public void prepareSendPartHeader_update(@Mocked Part part) {
+    new Expectations() {
+      {
+        part.getContentType();
+        result = "type";
+        part.getSubmittedFileName();
+        result = "name";
+      }
+    };
+    response.prepareSendPartHeader(part);
+
+    Assert.assertTrue(serverResponse.isChunked());
+    Assert.assertEquals("type", response.getHeader(HttpHeaders.CONTENT_TYPE));
+    Assert.assertEquals("attachment;filename=name", response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
+  }
+
+  @Test
+  public void prepareSendPartHeader_notUpdate(@Mocked Part part) {
+    headers.add(HttpHeaders.CONTENT_LENGTH, "10");
+    headers.add(HttpHeaders.CONTENT_TYPE, "type");
+    headers.add(HttpHeaders.CONTENT_DISPOSITION, "disposition");
+
+    response.prepareSendPartHeader(part);
+
+    Assert.assertFalse(serverResponse.isChunked());
+    Assert.assertEquals("type", response.getHeader(HttpHeaders.CONTENT_TYPE));
+    Assert.assertEquals("disposition", response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
+  }
+
+  @Test
+  public void sendPart_openInputStreamFailed(@Mocked Part part)
+      throws IOException, InterruptedException, ExecutionException {
+    IOException ioException = new IOException("forbid open stream");
+    new Expectations() {
+      {
+        part.getInputStream();
+        result = ioException;
+      }
+    };
+
+    CompletableFuture<Void> future = response.sendPart(part);
+
+    expectedException.expect(ExecutionException.class);
+    expectedException.expectCause(Matchers.sameInstance(ioException));
+
+    future.get();
+  }
+
+  @Test
+  public void sendPart_inputStreamBreak(@Mocked Part part, @Mocked InputStream inputStream)
+      throws IOException, InterruptedException, ExecutionException {
+    IOException ioException = new IOException("forbid read");
+    new Expectations() {
+      {
+        part.getInputStream();
+        result = inputStream;
+        inputStream.read((byte[]) any);
+        result = ioException;
+      }
+    };
+
+    CompletableFuture<Void> future = response.sendPart(part);
+
+    expectedException.expect(ExecutionException.class);
+    expectedException.expectCause(Matchers.sameInstance(ioException));
+
+    future.get();
+  }
+
+  @Test
+  public void sendPart_succ(@Mocked Part part, @Mocked InputStream inputStream)
+      throws IOException, InterruptedException, ExecutionException {
+    new Expectations() {
+      {
+        part.getInputStream();
+        result = inputStream;
+        inputStream.read((byte[]) any);
+        result = -1;
+      }
+    };
+
+    CompletableFuture<Void> future = response.sendPart(part);
+
+    Assert.assertNull(future.get());
+  }
 }

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