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/03 07:33:11 UTC

[servicecomb-java-chassis] branch master updated: [SCB-2140] change Response.headers to caseInsensitiveMultiMap to avoid bug cause by http2 feature (#2096)

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 66e4947  [SCB-2140] change Response.headers to caseInsensitiveMultiMap to avoid bug cause by http2 feature (#2096)
66e4947 is described below

commit 66e4947e07eefea00a25c6b9057dc842a6f2f768
Author: wujimin <wu...@huawei.com>
AuthorDate: Thu Dec 3 15:33:01 2020 +0800

    [SCB-2140] change Response.headers to caseInsensitiveMultiMap to avoid bug cause by http2 feature (#2096)
---
 .../element/impl/ResponseHeaderAccessItem.java     |  5 ++-
 .../core/element/impl/ResponseHeaderItemTest.java  | 20 +++------
 .../common/rest/AbstractRestInvocation.java        |  2 +-
 .../rest/filter/inner/RestServerCodecFilter.java   | 19 ++++----
 .../rest/resource/StaticResourceHandler.java       |  4 +-
 .../common/rest/TestAbstractRestInvocation.java    | 43 +++++++++---------
 .../filter/inner/RestServerCodecFilterTest.java    | 18 ++++----
 .../TestClassPathStaticResourceHandler.java        |  4 +-
 .../demo/jaxrs/server/CodeFirstJaxrs.java          |  6 +--
 .../multiErrorCode/MultiErrorCodeService.java      |  6 +--
 .../demo/springmvc/client/TestResponse.java        |  8 ++--
 .../demo/springmvc/server/CodeFirstSpringmvc.java  | 11 ++---
 .../inspector/internal/InspectorImpl.java          |  4 +-
 .../inspector/internal/TestInspectorImpl.java      | 20 ++++-----
 .../servicecomb/it/testcase/TestJsonView.java      |  2 +-
 .../demo/jaxrs/tests/endpoints/CodeFirstJaxrs.java |  6 +--
 .../tests/endpoints/CodeFirstSpringmvcBase.java    |  8 ++--
 .../springmvc/reference/CseClientHttpResponse.java | 16 +++----
 .../servicecomb/swagger/invocation/Response.java   | 51 ++++++++++++++++++++--
 .../response/JaxrsConsumerResponseMapper.java      | 18 +++-----
 .../response/JaxrsProducerResponseMapper.java      |  9 +++-
 .../response/TestJaxrsConsumerResponseMapper.java  |  4 +-
 .../response/TestJaxrsProducerResponseMapper.java  |  6 +--
 .../response/SpringmvcConsumerResponseMapper.java  | 18 +++-----
 .../response/SpringmvcProducerResponseMapper.java  |  6 +--
 .../TestSpringmvcConsumerResponseMapper.java       |  6 +--
 .../TestSpringmvcProducerResponseMapper.java       |  2 +-
 .../transport/highway/HighwayCodec.java            |  2 +-
 .../highway/HighwayServerCodecFilter.java          |  2 +-
 .../transport/highway/HighwayServerInvoke.java     |  2 +-
 .../transport/highway/message}/Headers.java        |  2 +-
 .../transport/highway/message/ResponseHeader.java  | 30 ++++++++++++-
 .../highway/HighwayServerCodecFilterTest.java      |  8 ++--
 .../transport/highway/message}/TestHeaders.java    |  2 +-
 .../rest/client/http/DefaultHttpClientFilter.java  |  2 +-
 .../client/http/TestDefaultHttpClientFilter.java   |  4 +-
 36 files changed, 208 insertions(+), 168 deletions(-)

diff --git a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/ResponseHeaderAccessItem.java b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/ResponseHeaderAccessItem.java
index 5b404f4..e87962f9 100644
--- a/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/ResponseHeaderAccessItem.java
+++ b/common/common-access-log/src/main/java/org/apache/servicecomb/common/accessLog/core/element/impl/ResponseHeaderAccessItem.java
@@ -49,11 +49,12 @@ public class ResponseHeaderAccessItem implements AccessLogItem<RoutingContext> {
   @Override
   public void appendClientFormattedItem(InvocationFinishEvent finishEvent, StringBuilder builder) {
     Response response = finishEvent.getResponse();
-    if (null == response || null == response.getHeaders() || null == response.getHeaders().getFirst(varName)) {
+    String value = response != null ? response.getHeader(varName) : null;
+    if (null == value) {
       builder.append(RESULT_NOT_FOUND);
       return;
     }
-    builder.append(response.getHeaders().getFirst(varName));
+    builder.append(value);
   }
 
   public String getVarName() {
diff --git a/common/common-access-log/src/test/java/org/apache/servicecomb/common/accessLog/core/element/impl/ResponseHeaderItemTest.java b/common/common-access-log/src/test/java/org/apache/servicecomb/common/accessLog/core/element/impl/ResponseHeaderItemTest.java
index dc209b6..30321b8 100644
--- a/common/common-access-log/src/test/java/org/apache/servicecomb/common/accessLog/core/element/impl/ResponseHeaderItemTest.java
+++ b/common/common-access-log/src/test/java/org/apache/servicecomb/common/accessLog/core/element/impl/ResponseHeaderItemTest.java
@@ -20,19 +20,14 @@ package org.apache.servicecomb.common.accessLog.core.element.impl;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.when;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 import org.apache.servicecomb.core.event.InvocationFinishEvent;
 import org.apache.servicecomb.core.event.ServerAccessLogEvent;
 import org.apache.servicecomb.swagger.invocation.Response;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import io.vertx.core.MultiMap;
 import io.vertx.core.http.HttpServerResponse;
 import io.vertx.core.http.impl.headers.VertxHttpHeaders;
 import io.vertx.ext.web.RoutingContext;
@@ -83,13 +78,10 @@ public class ResponseHeaderItemTest {
   @Test
   public void clientFormattedElement() {
     String headerValue = "headerValue";
-    Headers headers = new Headers();
-    Map<String, List<Object>> headerMap = new HashMap<>();
-    headerMap.put(VAR_NAME, Arrays.asList(headerValue));
 
-    headers.setHeaderMap(headerMap);
+    response = Response.ok(null)
+        .setHeader(VAR_NAME, headerValue);
     when(finishEvent.getResponse()).thenReturn(response);
-    when(response.getHeaders()).thenReturn(headers);
 
     ELEMENT.appendClientFormattedItem(finishEvent, strBuilder);
     assertEquals(headerValue, strBuilder.toString());
@@ -143,10 +135,8 @@ public class ResponseHeaderItemTest {
   @Test
   public void clientFormattedElementOnNotFound() {
     String headerValue = "headerValue";
-    Headers headers = new Headers();
-    Map<String, List<Object>> headerMap = new HashMap<>();
-    headerMap.put("anotherHeader", Arrays.asList(headerValue));
-    headers.setHeaderMap(headerMap);
+    MultiMap headers = MultiMap.caseInsensitiveMultiMap();
+    headers.set("anotherHeader", headerValue);
     when(finishEvent.getResponse()).thenReturn(response);
     when(response.getHeaders()).thenReturn(headers);
 
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java
index f0bc2ca..2304022 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/AbstractRestInvocation.java
@@ -268,7 +268,7 @@ public abstract class AbstractRestInvocation {
 
   @SuppressWarnings("deprecation")
   protected void sendResponse(Response response) {
-    RestServerCodecFilter.copyHeadersToHttpResponse(response.getHeaders().getHeaderMap(), responseEx);
+    RestServerCodecFilter.copyHeadersToHttpResponse(response.getHeaders(), responseEx);
 
     responseEx.setStatus(response.getStatusCode(), response.getReasonPhrase());
     responseEx.setAttribute(RestConst.INVOCATION_HANDLER_RESPONSE, response);
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java
index 901ca9c..bcf30a5 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilter.java
@@ -23,7 +23,6 @@ import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
 import static org.apache.servicecomb.core.exception.Exceptions.exceptionToResponse;
 import static org.apache.servicecomb.swagger.invocation.InvocationType.PRODUCER;
 
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.concurrent.CompletableFuture;
@@ -47,6 +46,7 @@ import org.apache.servicecomb.foundation.vertx.stream.BufferOutputStream;
 import org.apache.servicecomb.swagger.invocation.Response;
 
 import io.netty.buffer.Unpooled;
+import io.vertx.core.MultiMap;
 
 @FilterMeta(name = "rest-server-codec", invocationType = PRODUCER)
 public class RestServerCodecFilter implements Filter {
@@ -86,7 +86,7 @@ public class RestServerCodecFilter implements Filter {
   public static CompletableFuture<Response> encodeResponse(Response response, boolean download,
       ProduceProcessor produceProcessor, HttpServletResponseEx responseEx) {
     responseEx.setStatus(response.getStatusCode(), response.getReasonPhrase());
-    copyHeadersToHttpResponse(response.getHeaders().getHeaderMap(), responseEx);
+    copyHeadersToHttpResponse(response.getHeaders(), responseEx);
 
     if (download) {
       return CompletableFuture.completedFuture(response);
@@ -115,18 +115,15 @@ public class RestServerCodecFilter implements Filter {
         invocation.findResponseType(response.getStatusCode()).getRawClass());
   }
 
-  public static void copyHeadersToHttpResponse(Map<String, List<Object>> headerMap, HttpServletResponseEx responseEx) {
-    if (headerMap == null) {
+  public static void copyHeadersToHttpResponse(MultiMap headers, HttpServletResponseEx responseEx) {
+    if (headers == null) {
       return;
     }
 
-    for (Entry<String, List<Object>> entry : headerMap.entrySet()) {
-      for (Object value : entry.getValue()) {
-        if (!entry.getKey().equalsIgnoreCase(CONTENT_LENGTH)
-            && !entry.getKey().equalsIgnoreCase(TRANSFER_ENCODING)) {
-          responseEx.addHeader(entry.getKey(), String.valueOf(value));
-        }
-      }
+    headers.remove(CONTENT_LENGTH);
+    headers.remove(TRANSFER_ENCODING);
+    for (Entry<String, String> entry : headers.entries()) {
+      responseEx.addHeader(entry.getKey(), entry.getValue());
     }
   }
 }
diff --git a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/resource/StaticResourceHandler.java b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/resource/StaticResourceHandler.java
index 35421bc..e565532 100644
--- a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/resource/StaticResourceHandler.java
+++ b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/resource/StaticResourceHandler.java
@@ -66,8 +66,8 @@ public abstract class StaticResourceHandler {
   public Response handler(Part part) {
     // todo: cache control
     Response response = Response.ok(part);
-    response.getHeaders().addHeader(HttpHeaders.CONTENT_TYPE, part.getContentType());
-    response.getHeaders().addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline");
+    response.setHeader(HttpHeaders.CONTENT_TYPE, part.getContentType());
+    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline");
     return response;
   }
 }
diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java
index b3d30d8..0cb92a8 100644
--- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java
+++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/TestAbstractRestInvocation.java
@@ -17,6 +17,9 @@
 
 package org.apache.servicecomb.common.rest;
 
+import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
+import static com.google.common.net.HttpHeaders.TRANSFER_ENCODING;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import java.util.Arrays;
@@ -69,7 +72,6 @@ import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.context.HttpStatus;
 import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
 import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 import org.hamcrest.Matchers;
 import org.junit.After;
 import org.junit.Assert;
@@ -80,6 +82,7 @@ import org.junit.rules.ExpectedException;
 
 import com.google.common.eventbus.Subscribe;
 
+import io.vertx.core.MultiMap;
 import io.vertx.core.buffer.Buffer;
 import mockit.Deencapsulation;
 import mockit.Expectations;
@@ -630,8 +633,11 @@ public class TestAbstractRestInvocation {
   }
 
   @Test
-  public void testDoSendResponseHeaderNull(@Mocked Response response) {
-    Headers headers = new Headers();
+  public void should_ignore_content_length_and_transfer_encoding_when_copy_header_to_http_response(
+      @Mocked Response response) {
+    MultiMap headers = MultiMap.caseInsensitiveMultiMap()
+        .set(CONTENT_LENGTH, "10")
+        .set(TRANSFER_ENCODING, "encoding");
 
     new Expectations() {
       {
@@ -642,7 +648,7 @@ public class TestAbstractRestInvocation {
       }
     };
 
-    Headers resultHeaders = new Headers();
+    MultiMap resultHeaders = MultiMap.caseInsensitiveMultiMap();
     responseEx = new MockUp<HttpServletResponseEx>() {
       private Map<String, Object> attributes = new HashMap<>();
 
@@ -658,27 +664,24 @@ public class TestAbstractRestInvocation {
 
       @Mock
       void addHeader(String name, String value) {
-        resultHeaders.addHeader(name, value);
+        resultHeaders.add(name, value);
       }
     }.getMockInstance();
 
     invocation.onStart(0);
     initRestInvocation();
 
-    try {
-      restInvocation.sendResponse(response);
-      Assert.fail("must throw exception");
-    } catch (Error e) {
-      Assert.assertNull(resultHeaders.getHeaderMap());
-    }
+    restInvocation.sendResponse(response);
+    assertThat(headers).isEmpty();
+    assertThat(resultHeaders).isEmpty();
   }
 
   @Test
   public void testDoSendResponseHeaderNormal(@Mocked Response response) {
-    Headers headers = new Headers();
-    headers.addHeader("h1", "h1v1");
-    headers.addHeader("h1", "h1v2");
-    headers.addHeader("h2", "h2v");
+    MultiMap headers = MultiMap.caseInsensitiveMultiMap();
+    headers.add("h1", "h1v1");
+    headers.add("h1", "h1v2");
+    headers.add("h2", "h2v");
 
     new Expectations() {
       {
@@ -689,7 +692,7 @@ public class TestAbstractRestInvocation {
       }
     };
 
-    Headers resultHeaders = new Headers();
+    MultiMap resultHeaders = MultiMap.caseInsensitiveMultiMap();
     responseEx = new MockUp<HttpServletResponseEx>() {
       private Map<String, Object> attributes = new HashMap<>();
 
@@ -705,7 +708,7 @@ public class TestAbstractRestInvocation {
 
       @Mock
       void addHeader(String name, String value) {
-        resultHeaders.addHeader(name, value);
+        resultHeaders.add(name, value);
       }
     }.getMockInstance();
 
@@ -716,7 +719,7 @@ public class TestAbstractRestInvocation {
       restInvocation.sendResponse(response);
       Assert.fail("must throw exception");
     } catch (Error e) {
-      assertEquals(headers.getHeaderMap(), resultHeaders.getHeaderMap());
+      assertEquals(headers.toString(), resultHeaders.toString());
     }
   }
 
@@ -759,8 +762,8 @@ public class TestAbstractRestInvocation {
 
   @Test
   public void testDoSendResponseResultOKFilter(@Mocked Response response) {
-    Headers headers = new Headers();
-    headers.addHeader("Content-Type", "application/json");
+    MultiMap headers = MultiMap.caseInsensitiveMultiMap();
+    headers.set("Content-Type", "application/json");
     new Expectations() {
       {
         response.getHeaders();
diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilterTest.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilterTest.java
index 2e6fe77..8b887b6 100644
--- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilterTest.java
+++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/filter/inner/RestServerCodecFilterTest.java
@@ -43,7 +43,6 @@ import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
 import org.apache.servicecomb.foundation.test.scaffolding.exception.RuntimeExceptionWithoutStackTrace;
 import org.apache.servicecomb.foundation.vertx.http.HttpServletResponseEx;
 import org.apache.servicecomb.swagger.invocation.Response;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -51,6 +50,7 @@ import org.junit.Test;
 
 import com.fasterxml.jackson.databind.type.TypeFactory;
 
+import io.vertx.core.MultiMap;
 import io.vertx.core.json.Json;
 import mockit.Expectations;
 import mockit.Mocked;
@@ -76,7 +76,7 @@ public class RestServerCodecFilterTest {
   @Mocked
   HttpServletResponseEx responseEx;
 
-  Headers headers = new Headers();
+  MultiMap headers = MultiMap.caseInsensitiveMultiMap();
 
   FilterNode nextNode = new FilterNode((invocation, next) -> {
     Response response = Response.ok("ok");
@@ -169,7 +169,7 @@ public class RestServerCodecFilterTest {
 
   @Test
   public void should_encode_response_header() throws ExecutionException, InterruptedException {
-    headers.addHeader("h1", "v1");
+    headers.add("h1", "v1");
     success_invocation();
 
     new Verifications() {
@@ -185,9 +185,9 @@ public class RestServerCodecFilterTest {
 
   @Test
   public void should_not_encode_content_length_header() throws ExecutionException, InterruptedException {
-    headers.addHeader("h1", "v1")
-        .addHeader("h2", "v2")
-        .addHeader(CONTENT_LENGTH, 10);
+    headers.add("h1", "v1")
+        .add("h2", "v2")
+        .add(CONTENT_LENGTH, "10");
     success_invocation();
 
     new Verifications() {
@@ -203,9 +203,9 @@ public class RestServerCodecFilterTest {
 
   @Test
   public void should_not_encode_transfer_encoding_header() throws ExecutionException, InterruptedException {
-    headers.addHeader("h1", "v1")
-        .addHeader("h2", "v2")
-        .addHeader(TRANSFER_ENCODING, "test");
+    headers.add("h1", "v1")
+        .add("h2", "v2")
+        .add(TRANSFER_ENCODING, "test");
     success_invocation();
 
     new Verifications() {
diff --git a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/resource/TestClassPathStaticResourceHandler.java b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/resource/TestClassPathStaticResourceHandler.java
index 95a878d..7005eaa 100644
--- a/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/resource/TestClassPathStaticResourceHandler.java
+++ b/common/common-rest/src/test/java/org/apache/servicecomb/common/rest/resource/TestClassPathStaticResourceHandler.java
@@ -53,8 +53,8 @@ public class TestClassPathStaticResourceHandler {
       Assert.assertTrue(IOUtils.toString(is, StandardCharsets.UTF_8).endsWith("<html></html>"));
     }
     Assert.assertEquals("text/html", part.getContentType());
-    Assert.assertEquals("text/html", response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
-    Assert.assertEquals("inline", response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION));
+    Assert.assertEquals("text/html", response.getHeader(HttpHeaders.CONTENT_TYPE));
+    Assert.assertEquals("inline", response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
   }
 
   @Test
diff --git a/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/CodeFirstJaxrs.java b/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/CodeFirstJaxrs.java
index 1bb594a..8492437 100644
--- a/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/CodeFirstJaxrs.java
+++ b/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/CodeFirstJaxrs.java
@@ -58,7 +58,6 @@ import org.apache.servicecomb.swagger.extend.annotations.ResponseHeaders;
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.context.ContextUtils;
 import org.apache.servicecomb.swagger.invocation.context.InvocationContext;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
@@ -77,11 +76,10 @@ public class CodeFirstJaxrs {
   @GET
   public Response cseResponse(InvocationContext c1) {
     Response response = Response.createSuccess(Status.ACCEPTED, new User());
-    Headers headers = response.getHeaders();
-    headers.addHeader("h1", "h1v " + c1.getContext().get(Const.SRC_MICROSERVICE).toString());
+    response.setHeader("h1", "h1v " + c1.getContext().get(Const.SRC_MICROSERVICE));
 
     InvocationContext c2 = ContextUtils.getInvocationContext();
-    headers.addHeader("h2", "h2v " + c2.getContext().get(Const.SRC_MICROSERVICE).toString());
+    response.setHeader("h2", "h2v " + c2.getContext().get(Const.SRC_MICROSERVICE));
 
     return response;
   }
diff --git a/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/multiErrorCode/MultiErrorCodeService.java b/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/multiErrorCode/MultiErrorCodeService.java
index 5535c53..7f76e6a 100644
--- a/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/multiErrorCode/MultiErrorCodeService.java
+++ b/demo/demo-jaxrs/jaxrs-server/src/main/java/org/apache/servicecomb/demo/jaxrs/server/multiErrorCode/MultiErrorCodeService.java
@@ -82,14 +82,14 @@ public class MultiErrorCodeService {
       // If got many types for different status code, we can only using InvocationException for failed error code like 400-500.
       // The result for Failed Family(e.g. 400-500), can not set return value as target type directly or will give exception.
       response.setResult(new InvocationException(Status.BAD_REQUEST, r));
-      response.getHeaders().addHeader("x-code", "400");
+      response.setHeader("x-code", "400");
     } else if (request.getCode() == 500) {
       MultiResponse500 r = new MultiResponse500();
       r.setCode(500);
       r.setMessage("internal error");
       response.setStatus(Status.INTERNAL_SERVER_ERROR);
       response.setResult(new InvocationException(Status.INTERNAL_SERVER_ERROR, r));
-      response.getHeaders().addHeader("x-code", "500");
+      response.setHeader("x-code", "500");
     } else {
       MultiResponse200 r = new MultiResponse200();
       r.setCode(200);
@@ -97,7 +97,7 @@ public class MultiErrorCodeService {
       response.setStatus(Status.OK);
       // If error code is OK family(like 200), we can use the target type.
       response.setResult(r);
-      response.getHeaders().addHeader("x-code", "200");
+      response.setHeader("x-code", "200");
     }
     return response;
   }
diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestResponse.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestResponse.java
index 2a3edbd..cae0c13 100644
--- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestResponse.java
+++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestResponse.java
@@ -64,8 +64,8 @@ public class TestResponse {
     String srcName = RegistrationManager.INSTANCE.getMicroservice().getServiceName();
     Response cseResponse = intf.cseResponse();
     TestMgr.check("User [name=nameA, age=100, index=0]", cseResponse.getResult());
-    TestMgr.check("h1v " + srcName, cseResponse.getHeaders().getFirst("h1"));
-    TestMgr.check("h2v " + srcName, cseResponse.getHeaders().getFirst("h2"));
+    TestMgr.check("h1v " + srcName, cseResponse.getHeader("h1"));
+    TestMgr.check("h2v " + srcName, cseResponse.getHeader("h2"));
     TestMgr.check(cseResponse.getStatusCode(), 202);
   }
 
@@ -73,8 +73,8 @@ public class TestResponse {
     String srcName = RegistrationManager.INSTANCE.getMicroservice().getServiceName();
     Response cseResponse = intf.cseResponseCorrect();
     TestMgr.check("User [name=nameA, age=100, index=0]", cseResponse.getResult());
-    TestMgr.check("h1v " + srcName, cseResponse.getHeaders().getFirst("h1"));
-    TestMgr.check("h2v " + srcName, cseResponse.getHeaders().getFirst("h2"));
+    TestMgr.check("h1v " + srcName, cseResponse.getHeader("h1"));
+    TestMgr.check("h2v " + srcName, cseResponse.getHeader("h2"));
     TestMgr.check(cseResponse.getStatusCode(), 202);
   }
 
diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CodeFirstSpringmvc.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CodeFirstSpringmvc.java
index 52fbe6d..bbd1b17 100644
--- a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CodeFirstSpringmvc.java
+++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CodeFirstSpringmvc.java
@@ -56,7 +56,6 @@ import org.apache.servicecomb.swagger.invocation.context.ContextUtils;
 import org.apache.servicecomb.swagger.invocation.context.InvocationContext;
 import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
 import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpHeaders;
@@ -162,11 +161,10 @@ public class CodeFirstSpringmvc {
   @RequestMapping(path = "/cseResponse", method = RequestMethod.GET)
   public Response cseResponse(InvocationContext c1) {
     Response response = Response.createSuccess(Status.ACCEPTED, new User());
-    Headers headers = response.getHeaders();
-    headers.addHeader("h1", "h1v " + c1.getContext().get(Const.SRC_MICROSERVICE));
+    response.addHeader("h1", "h1v " + c1.getContext().get(Const.SRC_MICROSERVICE));
 
     InvocationContext c2 = ContextUtils.getInvocationContext();
-    headers.addHeader("h2", "h2v " + c2.getContext().get(Const.SRC_MICROSERVICE));
+    response.addHeader("h2", "h2v " + c2.getContext().get(Const.SRC_MICROSERVICE));
 
     return response;
   }
@@ -179,11 +177,10 @@ public class CodeFirstSpringmvc {
   @RequestMapping(path = "/cseResponseCorrect", method = RequestMethod.GET)
   public Response cseResponseCorrect(InvocationContext c1) {
     Response response = Response.createSuccess(Status.ACCEPTED, new User());
-    Headers headers = response.getHeaders();
-    headers.addHeader("h1", "h1v " + c1.getContext().get(Const.SRC_MICROSERVICE));
+    response.addHeader("h1", "h1v " + c1.getContext().get(Const.SRC_MICROSERVICE));
 
     InvocationContext c2 = ContextUtils.getInvocationContext();
-    headers.addHeader("h2", "h2v " + c2.getContext().get(Const.SRC_MICROSERVICE));
+    response.addHeader("h2", "h2v " + c2.getContext().get(Const.SRC_MICROSERVICE));
 
     return response;
   }
diff --git a/inspector/src/main/java/org/apache/servicecomb/inspector/internal/InspectorImpl.java b/inspector/src/main/java/org/apache/servicecomb/inspector/internal/InspectorImpl.java
index 5995fb7..046f46e 100644
--- a/inspector/src/main/java/org/apache/servicecomb/inspector/internal/InspectorImpl.java
+++ b/inspector/src/main/java/org/apache/servicecomb/inspector/internal/InspectorImpl.java
@@ -218,9 +218,9 @@ public class InspectorImpl {
 
     Response response = Response.ok(part);
     if (!download) {
-      response.getHeaders().addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline");
+      response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline");
     }
-    response.getHeaders().addHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
+    response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
     return response;
   }
 
diff --git a/inspector/src/test/java/org/apache/servicecomb/inspector/internal/TestInspectorImpl.java b/inspector/src/test/java/org/apache/servicecomb/inspector/internal/TestInspectorImpl.java
index c55aae7..9280442 100644
--- a/inspector/src/test/java/org/apache/servicecomb/inspector/internal/TestInspectorImpl.java
+++ b/inspector/src/test/java/org/apache/servicecomb/inspector/internal/TestInspectorImpl.java
@@ -235,8 +235,8 @@ public class TestInspectorImpl {
 
     Part part = response.getResult();
     Assert.assertEquals(schemaId + ".yaml", part.getSubmittedFileName());
-    Assert.assertEquals("inline", response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION));
-    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
+    Assert.assertEquals("inline", response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
+    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeader(HttpHeaders.CONTENT_TYPE));
 
     try (InputStream is = part.getInputStream()) {
       Assert.assertEquals(schemas.get(schemaId), IOUtils.toString(is, StandardCharsets.UTF_8));
@@ -256,8 +256,8 @@ public class TestInspectorImpl {
 
     Part part = response.getResult();
     Assert.assertEquals(schemaId + ".yaml", part.getSubmittedFileName());
-    Assert.assertNull(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION));
-    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
+    Assert.assertNull(response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
+    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeader(HttpHeaders.CONTENT_TYPE));
 
     try (InputStream is = part.getInputStream()) {
       Assert.assertEquals(schemas.get(schemaId), IOUtils.toString(is, StandardCharsets.UTF_8));
@@ -275,8 +275,8 @@ public class TestInspectorImpl {
 
     Part part = response.getResult();
     Assert.assertEquals(schemaId + ".html", part.getSubmittedFileName());
-    Assert.assertEquals("inline", response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION));
-    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
+    Assert.assertEquals("inline", response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
+    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeader(HttpHeaders.CONTENT_TYPE));
 
     try (InputStream is = part.getInputStream()) {
       Assert.assertTrue(IOUtils.toString(is, StandardCharsets.UTF_8).endsWith("</html>"));
@@ -294,8 +294,8 @@ public class TestInspectorImpl {
 
     Part part = response.getResult();
     Assert.assertEquals(schemaId + ".html", part.getSubmittedFileName());
-    Assert.assertNull(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION));
-    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
+    Assert.assertNull(response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
+    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeader(HttpHeaders.CONTENT_TYPE));
 
     try (InputStream is = part.getInputStream()) {
       Assert.assertTrue(IOUtils.toString(is, StandardCharsets.UTF_8).endsWith("</html>"));
@@ -319,8 +319,8 @@ public class TestInspectorImpl {
     Response response = inspector.getStaticResource("index.html");
 
     Part part = response.getResult();
-    Assert.assertEquals("inline", response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION));
-    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
+    Assert.assertEquals("inline", response.getHeader(HttpHeaders.CONTENT_DISPOSITION));
+    Assert.assertEquals(MediaType.TEXT_HTML, response.getHeader(HttpHeaders.CONTENT_TYPE));
 
     try (InputStream is = part.getInputStream()) {
       Assert.assertTrue(IOUtils.toString(is, StandardCharsets.UTF_8).endsWith("</html>"));
diff --git a/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestJsonView.java b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestJsonView.java
index cb53270..6a44e24 100644
--- a/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestJsonView.java
+++ b/integration-tests/it-consumer/src/main/java/org/apache/servicecomb/it/testcase/TestJsonView.java
@@ -214,7 +214,7 @@ public class TestJsonView {
   }
 
   @Test
-  public void testJsonViewPlainDefaultWithSummary() {
+  public void should_not_affect_text_plain_by_json_view() {
     String restPersonViewSpringMvc = consumersSpringmvc.getSCBRestTemplate()
         .getForObject("/jsonViewPlainDefaultWithSummary", String.class);
     assertEquals(EXPECT_NO_VIEW.toString(), restPersonViewSpringMvc);
diff --git a/integration-tests/jaxrs-tests/src/test/java/org/apache/servicecomb/demo/jaxrs/tests/endpoints/CodeFirstJaxrs.java b/integration-tests/jaxrs-tests/src/test/java/org/apache/servicecomb/demo/jaxrs/tests/endpoints/CodeFirstJaxrs.java
index 1940c7f..7e3b6d0 100644
--- a/integration-tests/jaxrs-tests/src/test/java/org/apache/servicecomb/demo/jaxrs/tests/endpoints/CodeFirstJaxrs.java
+++ b/integration-tests/jaxrs-tests/src/test/java/org/apache/servicecomb/demo/jaxrs/tests/endpoints/CodeFirstJaxrs.java
@@ -36,7 +36,6 @@ import org.apache.servicecomb.swagger.extend.annotations.ResponseHeaders;
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.context.ContextUtils;
 import org.apache.servicecomb.swagger.invocation.context.InvocationContext;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
@@ -54,11 +53,10 @@ public class CodeFirstJaxrs extends SomeAbstractJaxrsRestEndpoint {
   @GET
   public Response response(InvocationContext c1) {
     Response response = Response.createSuccess(Status.ACCEPTED, new User());
-    Headers headers = response.getHeaders();
-    headers.addHeader("h1", "h1v " + c1.getContext().toString());
+    response.addHeader("h1", "h1v " + c1.getContext().toString());
 
     InvocationContext c2 = ContextUtils.getInvocationContext();
-    headers.addHeader("h2", "h2v " + c2.getContext().toString());
+    response.addHeader("h2", "h2v " + c2.getContext().toString());
 
     return response;
   }
diff --git a/integration-tests/springmvc-tests/common/src/test/java/org/apache/servicecomb/demo/springmvc/tests/endpoints/CodeFirstSpringmvcBase.java b/integration-tests/springmvc-tests/common/src/test/java/org/apache/servicecomb/demo/springmvc/tests/endpoints/CodeFirstSpringmvcBase.java
index 912da18..c3131ce 100644
--- a/integration-tests/springmvc-tests/common/src/test/java/org/apache/servicecomb/demo/springmvc/tests/endpoints/CodeFirstSpringmvcBase.java
+++ b/integration-tests/springmvc-tests/common/src/test/java/org/apache/servicecomb/demo/springmvc/tests/endpoints/CodeFirstSpringmvcBase.java
@@ -33,7 +33,6 @@ import org.apache.servicecomb.demo.server.User;
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.context.ContextUtils;
 import org.apache.servicecomb.swagger.invocation.context.InvocationContext;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -47,16 +46,15 @@ public class CodeFirstSpringmvcBase {
     InvocationContext c2 = ContextUtils.getInvocationContext();
     headers.add("h2", "h2v " + c2.getContext().toString());
 
-    return new ResponseEntity<Date>(date, headers, HttpStatus.ACCEPTED);
+    return new ResponseEntity<>(date, headers, HttpStatus.ACCEPTED);
   }
 
   public Response cseResponse(InvocationContext c1) {
     Response response = Response.createSuccess(Status.ACCEPTED, new User());
-    Headers headers = response.getHeaders();
-    headers.addHeader("h1", "h1v " + c1.getContext().toString());
+    response.addHeader("h1", "h1v " + c1.getContext().toString());
 
     InvocationContext c2 = ContextUtils.getInvocationContext();
-    headers.addHeader("h2", "h2v " + c2.getContext().toString());
+    response.addHeader("h2", "h2v " + c2.getContext().toString());
 
     return response;
   }
diff --git a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpResponse.java b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpResponse.java
index a06160a..f8b3522 100644
--- a/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpResponse.java
+++ b/providers/provider-springmvc/src/main/java/org/apache/servicecomb/provider/springmvc/reference/CseClientHttpResponse.java
@@ -19,15 +19,15 @@ package org.apache.servicecomb.provider.springmvc.reference;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.List;
 import java.util.Map.Entry;
 
 import org.apache.servicecomb.swagger.invocation.Response;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.client.ClientHttpResponse;
 
+import io.vertx.core.MultiMap;
+
 /**
  * cse应答在transport层已经完成了码流到对象的转换
  * 这里是适配springmvc的机制,让调用者能拿到已经转换好的对象
@@ -36,6 +36,7 @@ public class CseClientHttpResponse implements ClientHttpResponse {
   // 让springmvc client以为应答有body
   // mark、reset都是有锁的,这里通过重写取消了锁
   private static final InputStream BODY_INPUT_STREAM = new InputStream() {
+    @Override
     public boolean markSupported() {
       return true;
     }
@@ -49,6 +50,7 @@ public class CseClientHttpResponse implements ClientHttpResponse {
       return 0;
     }
 
+    @Override
     public void reset() throws IOException {
     }
   };
@@ -74,12 +76,10 @@ public class CseClientHttpResponse implements ClientHttpResponse {
   public HttpHeaders getHeaders() {
     if (httpHeaders == null) {
       HttpHeaders tmpHeaders = new HttpHeaders();
-      Headers headers = response.getHeaders();
-      if (headers.getHeaderMap() != null) {
-        for (Entry<String, List<Object>> entry : headers.getHeaderMap().entrySet()) {
-          for (Object value : entry.getValue()) {
-            tmpHeaders.add(entry.getKey(), String.valueOf(value));
-          }
+      MultiMap headers = response.getHeaders();
+      if (headers != null) {
+        for (Entry<String, String> entry : headers.entries()) {
+          tmpHeaders.add(entry.getKey(), entry.getValue());
         }
       }
 
diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/Response.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/Response.java
index c5866f6..99c3b55 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/Response.java
+++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/Response.java
@@ -16,6 +16,8 @@
  */
 package org.apache.servicecomb.swagger.invocation;
 
+import java.util.List;
+
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.Response.StatusType;
 
@@ -23,7 +25,8 @@ import org.apache.servicecomb.swagger.invocation.context.HttpStatus;
 import org.apache.servicecomb.swagger.invocation.exception.CommonExceptionData;
 import org.apache.servicecomb.swagger.invocation.exception.ExceptionFactory;
 import org.apache.servicecomb.swagger.invocation.exception.InvocationException;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
+
+import io.vertx.core.MultiMap;
 
 /**
  * 用jaxrs的Response能表达所有概念
@@ -34,7 +37,7 @@ import org.apache.servicecomb.swagger.invocation.response.Headers;
 public class Response {
   private StatusType status;
 
-  private Headers headers = new Headers();
+  private MultiMap headers;
 
   // 失败场景中,result是Throwable
   private Object result;
@@ -63,14 +66,54 @@ public class Response {
     this.status = status;
   }
 
-  public Headers getHeaders() {
+  public String getHeader(String name) {
+    if (headers == null) {
+      return null;
+    }
+
+    return headers.get(name);
+  }
+
+  public List<String> getHeaders(String name) {
+    if (headers == null) {
+      return null;
+    }
+
+    return headers.getAll(name);
+  }
+
+  public MultiMap getHeaders() {
     return headers;
   }
 
-  public void setHeaders(Headers headers) {
+  public void setHeaders(MultiMap headers) {
     this.headers = headers;
   }
 
+  public Response addHeader(String name, String value) {
+    if (value == null) {
+      return this;
+    }
+    if (headers == null) {
+      headers = MultiMap.caseInsensitiveMultiMap();
+    }
+
+    headers.add(name, value);
+    return this;
+  }
+
+  public Response setHeader(String name, String value) {
+    if (value == null) {
+      return this;
+    }
+    if (headers == null) {
+      headers = MultiMap.caseInsensitiveMultiMap();
+    }
+
+    headers.set(name, value);
+    return this;
+  }
+
   @SuppressWarnings("unchecked")
   public <T> T getResult() {
     return (T) result;
diff --git a/swagger/swagger-invocation/invocation-jaxrs/src/main/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/JaxrsConsumerResponseMapper.java b/swagger/swagger-invocation/invocation-jaxrs/src/main/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/JaxrsConsumerResponseMapper.java
index f8b5373..e11638a 100644
--- a/swagger/swagger-invocation/invocation-jaxrs/src/main/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/JaxrsConsumerResponseMapper.java
+++ b/swagger/swagger-invocation/invocation-jaxrs/src/main/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/JaxrsConsumerResponseMapper.java
@@ -16,8 +16,6 @@
  */
 package org.apache.servicecomb.swagger.invocation.jaxrs.response;
 
-import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
 
 import javax.ws.rs.core.Response.ResponseBuilder;
@@ -25,27 +23,21 @@ import javax.ws.rs.core.Response.ResponseBuilder;
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.response.consumer.ConsumerResponseMapper;
 
+import io.vertx.core.MultiMap;
+
 public class JaxrsConsumerResponseMapper implements ConsumerResponseMapper {
   @Override
   public Object mapResponse(Response response) {
     ResponseBuilder responseBuilder =
         javax.ws.rs.core.Response.status(response.getStatus()).entity(response.getResult());
 
-    Map<String, List<Object>> headers = response.getHeaders().getHeaderMap();
+    MultiMap headers = response.getHeaders();
     if (headers == null) {
       return responseBuilder.build();
     }
 
-    for (Entry<String, List<Object>> entry : headers.entrySet()) {
-      if (entry.getValue() == null) {
-        continue;
-      }
-
-      for (Object value : entry.getValue()) {
-        if (value != null) {
-          responseBuilder.header(entry.getKey(), value);
-        }
-      }
+    for (Entry<String, String> entry : headers.entries()) {
+      responseBuilder.header(entry.getKey(), entry.getValue());
     }
 
     return responseBuilder.build();
diff --git a/swagger/swagger-invocation/invocation-jaxrs/src/main/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/JaxrsProducerResponseMapper.java b/swagger/swagger-invocation/invocation-jaxrs/src/main/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/JaxrsProducerResponseMapper.java
index f563e7a..4bc3afb 100644
--- a/swagger/swagger-invocation/invocation-jaxrs/src/main/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/JaxrsProducerResponseMapper.java
+++ b/swagger/swagger-invocation/invocation-jaxrs/src/main/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/JaxrsProducerResponseMapper.java
@@ -33,11 +33,16 @@ public class JaxrsProducerResponseMapper implements ProducerResponseMapper {
     Response cseResponse = Response.status(jaxrsResponse.getStatusInfo()).entity(jaxrsResponse.getEntity());
     MultivaluedMap<String, Object> headers = jaxrsResponse.getHeaders();
     for (Entry<String, List<Object>> entry : headers.entrySet()) {
-      if (entry.getValue() == null || entry.getValue().isEmpty()) {
+      if (entry.getValue() == null) {
         continue;
       }
 
-      cseResponse.getHeaders().addHeader(entry.getKey(), entry.getValue());
+      for (Object value : entry.getValue()) {
+        if (value == null) {
+          continue;
+        }
+        cseResponse.addHeader(entry.getKey(), value.toString());
+      }
     }
     return cseResponse;
   }
diff --git a/swagger/swagger-invocation/invocation-jaxrs/src/test/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/TestJaxrsConsumerResponseMapper.java b/swagger/swagger-invocation/invocation-jaxrs/src/test/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/TestJaxrsConsumerResponseMapper.java
index 423bfe3..fac885b 100644
--- a/swagger/swagger-invocation/invocation-jaxrs/src/test/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/TestJaxrsConsumerResponseMapper.java
+++ b/swagger/swagger-invocation/invocation-jaxrs/src/test/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/TestJaxrsConsumerResponseMapper.java
@@ -69,8 +69,8 @@ public class TestJaxrsConsumerResponseMapper {
   @Test
   public void jaxrsResponseWithHeaders() {
     SwaggerConsumerOperation operation = swaggerConsumer.findOperation("jaxrsResponse");
-    response.getHeaders().addHeader("h", "v1").addHeader("h", "v2").addHeader("h", (Object) null);
-    response.getHeaders().getHeaderMap().put("h1", null);
+    response.addHeader("h", "v1").addHeader("h", "v2").addHeader("h", null);
+    response.addHeader("h1", null);
 
     Response jaxrsResponse = (Response) operation.getResponseMapper().mapResponse(response);
     Assert.assertEquals(result, jaxrsResponse.getEntity());
diff --git a/swagger/swagger-invocation/invocation-jaxrs/src/test/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/TestJaxrsProducerResponseMapper.java b/swagger/swagger-invocation/invocation-jaxrs/src/test/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/TestJaxrsProducerResponseMapper.java
index adbda3a..74b3699 100644
--- a/swagger/swagger-invocation/invocation-jaxrs/src/test/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/TestJaxrsProducerResponseMapper.java
+++ b/swagger/swagger-invocation/invocation-jaxrs/src/test/java/org/apache/servicecomb/swagger/invocation/jaxrs/response/TestJaxrsProducerResponseMapper.java
@@ -47,7 +47,7 @@ public class TestJaxrsProducerResponseMapper {
     Response response = mapper.mapResponse(null, jaxrsResponse);
     Assert.assertEquals(Status.OK, response.getStatus());
     Assert.assertEquals("result", response.getResult());
-    Assert.assertNull(response.getHeaders().getHeaderMap());
+    Assert.assertNull(response.getHeaders());
   }
 
   @Test
@@ -68,7 +68,7 @@ public class TestJaxrsProducerResponseMapper {
     Response response = mapper.mapResponse(null, jaxrsResponse);
     Assert.assertEquals(Status.OK, response.getStatus());
     Assert.assertEquals("result", response.getResult());
-    Assert.assertEquals(1, response.getHeaders().getHeaderMap().size());
-    Assert.assertThat(response.getHeaders().getHeader("h"), Matchers.contains("v"));
+    Assert.assertEquals(1, response.getHeaders().size());
+    Assert.assertThat(response.getHeaders("h"), Matchers.contains("v"));
   }
 }
diff --git a/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/springmvc/response/SpringmvcConsumerResponseMapper.java b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/springmvc/response/SpringmvcConsumerResponseMapper.java
index b50ac32..624d9a0 100644
--- a/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/springmvc/response/SpringmvcConsumerResponseMapper.java
+++ b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/springmvc/response/SpringmvcConsumerResponseMapper.java
@@ -16,8 +16,6 @@
  */
 package org.apache.servicecomb.swagger.invocation.springmvc.response;
 
-import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
 
 import org.apache.servicecomb.swagger.invocation.Response;
@@ -26,6 +24,8 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 
+import io.vertx.core.MultiMap;
+
 public class SpringmvcConsumerResponseMapper implements ConsumerResponseMapper {
   private ConsumerResponseMapper realMapper;
 
@@ -37,23 +37,15 @@ public class SpringmvcConsumerResponseMapper implements ConsumerResponseMapper {
   public Object mapResponse(Response response) {
     HttpStatus status = HttpStatus.valueOf(response.getStatusCode());
 
-    Map<String, List<Object>> headers = response.getHeaders().getHeaderMap();
+    MultiMap headers = response.getHeaders();
     if (headers == null) {
       Object realResult = realMapper.mapResponse(response);
       return new ResponseEntity<>(realResult, null, status);
     }
 
     HttpHeaders httpHeaders = new HttpHeaders();
-    for (Entry<String, List<Object>> entry : headers.entrySet()) {
-      if (entry.getValue() == null) {
-        continue;
-      }
-
-      for (Object value : entry.getValue()) {
-        if (value != null) {
-          httpHeaders.add(entry.getKey(), String.valueOf(value));
-        }
-      }
+    for (Entry<String, String> entry : headers.entries()) {
+      httpHeaders.add(entry.getKey(), entry.getValue());
     }
 
     Object realResult = realMapper.mapResponse(response);
diff --git a/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/springmvc/response/SpringmvcProducerResponseMapper.java b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/springmvc/response/SpringmvcProducerResponseMapper.java
index 8f898e1..dd492fe 100644
--- a/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/springmvc/response/SpringmvcProducerResponseMapper.java
+++ b/swagger/swagger-invocation/invocation-springmvc/src/main/java/org/apache/servicecomb/swagger/invocation/springmvc/response/SpringmvcProducerResponseMapper.java
@@ -23,7 +23,6 @@ import javax.ws.rs.core.Response.StatusType;
 
 import org.apache.servicecomb.swagger.invocation.Response;
 import org.apache.servicecomb.swagger.invocation.context.HttpStatus;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 import org.apache.servicecomb.swagger.invocation.response.producer.ProducerResponseMapper;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.ResponseEntity;
@@ -52,14 +51,13 @@ public class SpringmvcProducerResponseMapper implements ProducerResponseMapper {
     }
 
     HttpHeaders headers = springmvcResponse.getHeaders();
-    Headers cseHeaders = cseResponse.getHeaders();
     for (Entry<String, List<String>> entry : headers.entrySet()) {
-      if (entry.getValue() == null || entry.getValue().isEmpty()) {
+      if (entry.getValue() == null) {
         continue;
       }
 
       for (String value : entry.getValue()) {
-        cseHeaders.addHeader(entry.getKey(), value);
+        cseResponse.addHeader(entry.getKey(), value);
       }
     }
     return cseResponse;
diff --git a/swagger/swagger-invocation/invocation-springmvc/src/test/java/org/apache/servicecomb/swagger/invocation/springmvc/response/TestSpringmvcConsumerResponseMapper.java b/swagger/swagger-invocation/invocation-springmvc/src/test/java/org/apache/servicecomb/swagger/invocation/springmvc/response/TestSpringmvcConsumerResponseMapper.java
index 3fb9f2e..9bbb7b3 100644
--- a/swagger/swagger-invocation/invocation-springmvc/src/test/java/org/apache/servicecomb/swagger/invocation/springmvc/response/TestSpringmvcConsumerResponseMapper.java
+++ b/swagger/swagger-invocation/invocation-springmvc/src/test/java/org/apache/servicecomb/swagger/invocation/springmvc/response/TestSpringmvcConsumerResponseMapper.java
@@ -66,7 +66,7 @@ public class TestSpringmvcConsumerResponseMapper {
   @Test
   public void responseEntityWithHeader() {
     SwaggerConsumerOperation operation = swaggerConsumer.findOperation("responseEntity");
-    response.getHeaders().addHeader("h", "v");
+    response.addHeader("h", "v");
 
     @SuppressWarnings("unchecked")
     ResponseEntity<String> responseEntity = (ResponseEntity<String>) operation.getResponseMapper()
@@ -90,8 +90,8 @@ public class TestSpringmvcConsumerResponseMapper {
   @Test
   public void asyncResponseEntityWithHeader() {
     SwaggerConsumerOperation operation = swaggerConsumer.findOperation("asyncResponseEntity");
-    response.getHeaders().addHeader("h", "v1").addHeader("h", "v2");
-    response.getHeaders().getHeaderMap().put("h1", null);
+    response.addHeader("h", "v1").addHeader("h", "v2");
+    response.addHeader("h1", null);
 
     @SuppressWarnings("unchecked")
     ResponseEntity<String> responseEntity = (ResponseEntity<String>) operation.getResponseMapper()
diff --git a/swagger/swagger-invocation/invocation-springmvc/src/test/java/org/apache/servicecomb/swagger/invocation/springmvc/response/TestSpringmvcProducerResponseMapper.java b/swagger/swagger-invocation/invocation-springmvc/src/test/java/org/apache/servicecomb/swagger/invocation/springmvc/response/TestSpringmvcProducerResponseMapper.java
index 4a397d1..594cae9 100644
--- a/swagger/swagger-invocation/invocation-springmvc/src/test/java/org/apache/servicecomb/swagger/invocation/springmvc/response/TestSpringmvcProducerResponseMapper.java
+++ b/swagger/swagger-invocation/invocation-springmvc/src/test/java/org/apache/servicecomb/swagger/invocation/springmvc/response/TestSpringmvcProducerResponseMapper.java
@@ -88,7 +88,7 @@ public class TestSpringmvcProducerResponseMapper {
         new ResponseEntity<>(arrResult, headers, org.springframework.http.HttpStatus.OK);
     Response response = mapper.mapResponse(null, responseEntity);
 
-    List<Object> hv = response.getHeaders().getHeader("h");
+    List<String> hv = response.getHeaders("h");
     Assert.assertThat(hv, Matchers.contains("v"));
   }
 }
diff --git a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayCodec.java b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayCodec.java
index a87ec0b..ac2bb96 100644
--- a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayCodec.java
+++ b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayCodec.java
@@ -111,7 +111,7 @@ public final class HighwayCodec {
 
     Response response = Response.create(header.getStatusCode(), header.getReasonPhrase()
         , defaultPrimitiveValue(body, type));
-    response.setHeaders(header.getHeaders());
+    response.setHeaders(header.toMultiMap());
 
     return response;
   }
diff --git a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilter.java b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilter.java
index 97acd91..c719ead 100644
--- a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilter.java
+++ b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilter.java
@@ -65,7 +65,7 @@ public class HighwayServerCodecFilter implements Filter {
     header.setStatusCode(response.getStatusCode());
     header.setReasonPhrase(response.getReasonPhrase());
     header.setContext(invocation.getContext());
-    header.setHeaders(response.getHeaders());
+    header.fromMultiMap(response.getHeaders());
 
     HighwayTransportContext transportContext = invocation.getTransportContext();
     long msgId = transportContext.getMsgId();
diff --git a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerInvoke.java b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerInvoke.java
index bf5ff12..2ffe63b 100644
--- a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerInvoke.java
+++ b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/HighwayServerInvoke.java
@@ -139,7 +139,7 @@ public class HighwayServerInvoke {
     header.setStatusCode(response.getStatusCode());
     header.setReasonPhrase(response.getReasonPhrase());
     header.setContext(context);
-    header.setHeaders(response.getHeaders());
+    header.fromMultiMap(response.getHeaders());
 
     ResponseRootSerializer bodySchema = operationProtobuf.findResponseRootSerializer(response.getStatusCode());
     Object body = response.getResult();
diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/response/Headers.java b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/message/Headers.java
similarity index 97%
rename from swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/response/Headers.java
rename to transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/message/Headers.java
index acdba5f..6dfe8c1 100644
--- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/response/Headers.java
+++ b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/message/Headers.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.servicecomb.swagger.invocation.response;
+package org.apache.servicecomb.transport.highway.message;
 
 import java.util.ArrayList;
 import java.util.HashMap;
diff --git a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/message/ResponseHeader.java b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/message/ResponseHeader.java
index 65488c2..83bc8a4 100644
--- a/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/message/ResponseHeader.java
+++ b/transports/transport-highway/src/main/java/org/apache/servicecomb/transport/highway/message/ResponseHeader.java
@@ -17,13 +17,15 @@
 
 package org.apache.servicecomb.transport.highway.message;
 
+import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import org.apache.servicecomb.foundation.protobuf.ProtoMapperFactory;
 import org.apache.servicecomb.foundation.protobuf.RootDeserializer;
 import org.apache.servicecomb.foundation.protobuf.RootSerializer;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 
+import io.vertx.core.MultiMap;
 import io.vertx.core.buffer.Buffer;
 
 public class ResponseHeader {
@@ -95,4 +97,30 @@ public class ResponseHeader {
   public void setHeaders(Headers headers) {
     this.headers = headers;
   }
+
+  public void fromMultiMap(MultiMap multiMap) {
+    if (multiMap == null) {
+      return;
+    }
+
+    for (Entry<String, String> entry : multiMap.entries()) {
+      headers.addHeader(entry.getKey(), entry.getValue());
+    }
+  }
+
+  public MultiMap toMultiMap() {
+    MultiMap multiMap = MultiMap.caseInsensitiveMultiMap();
+    Map<String, List<Object>> headerMap = headers.getHeaderMap();
+    if (headerMap == null) {
+      return multiMap;
+    }
+
+    for (Entry<String, List<Object>> entry : headerMap.entrySet()) {
+      String key = entry.getKey();
+      for (Object value : entry.getValue()) {
+        multiMap.add(key, value.toString());
+      }
+    }
+    return multiMap;
+  }
 }
diff --git a/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilterTest.java b/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilterTest.java
index d259d60..a331a2d 100644
--- a/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilterTest.java
+++ b/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/HighwayServerCodecFilterTest.java
@@ -36,7 +36,6 @@ import org.apache.servicecomb.core.invocation.InvocationFactory;
 import org.apache.servicecomb.foundation.test.scaffolding.config.ArchaiusUtils;
 import org.apache.servicecomb.foundation.test.scaffolding.exception.RuntimeExceptionWithoutStackTrace;
 import org.apache.servicecomb.swagger.invocation.Response;
-import org.apache.servicecomb.swagger.invocation.response.Headers;
 import org.apache.servicecomb.transport.highway.message.RequestHeader;
 import org.apache.servicecomb.transport.highway.message.ResponseHeader;
 import org.junit.AfterClass;
@@ -44,6 +43,7 @@ import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import io.vertx.core.MultiMap;
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.json.Json;
 import mockit.Expectations;
@@ -64,7 +64,7 @@ public class HighwayServerCodecFilterTest {
   @Mocked
   HighwayTransportContext transportContext;
 
-  Headers headers = new Headers();
+  MultiMap headers = MultiMap.caseInsensitiveMultiMap();
 
   FilterNode nextNode = new FilterNode((invocation, next) -> {
     Response response = Response.ok("ok");
@@ -153,8 +153,8 @@ public class HighwayServerCodecFilterTest {
 
     new Verifications() {
       {
-        Headers captureHeaders;
-        responseHeader.setHeaders(captureHeaders = withCapture());
+        MultiMap captureHeaders;
+        responseHeader.fromMultiMap(captureHeaders = withCapture());
         assertThat(captureHeaders).isSameAs(headers);
       }
     };
diff --git a/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/response/TestHeaders.java b/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/message/TestHeaders.java
similarity index 97%
rename from swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/response/TestHeaders.java
rename to transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/message/TestHeaders.java
index f248661..3a0a098 100644
--- a/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/response/TestHeaders.java
+++ b/transports/transport-highway/src/test/java/org/apache/servicecomb/transport/highway/message/TestHeaders.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.servicecomb.swagger.invocation.response;
+package org.apache.servicecomb.transport.highway.message;
 
 import java.util.Arrays;
 import java.util.HashMap;
diff --git a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java
index 1e60b06..6b0852c 100644
--- a/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java
+++ b/transports/transport-rest/transport-rest-client/src/main/java/org/apache/servicecomb/transport/rest/client/http/DefaultHttpClientFilter.java
@@ -133,7 +133,7 @@ public class DefaultHttpClientFilter implements HttpClientFilter {
       }
       Collection<String> headerValues = responseEx.getHeaders(headerName);
       for (String headerValue : headerValues) {
-        response.getHeaders().addHeader(headerName, headerValue);
+        response.addHeader(headerName, headerValue);
       }
     }
 
diff --git a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestDefaultHttpClientFilter.java b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestDefaultHttpClientFilter.java
index e04db0c..adf9eab 100644
--- a/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestDefaultHttpClientFilter.java
+++ b/transports/transport-rest/transport-rest-client/src/test/java/org/apache/servicecomb/transport/rest/client/http/TestDefaultHttpClientFilter.java
@@ -287,7 +287,7 @@ public class TestDefaultHttpClientFilter {
 
     Response response = filter.afterReceiveResponse(invocation, responseEx);
     Assert.assertSame(decodedResult, response.getResult());
-    Assert.assertEquals(1, response.getHeaders().getHeaderMap().size());
-    Assert.assertEquals(response.getHeaders().getHeader("b"), Arrays.asList("bValue"));
+    Assert.assertEquals(1, response.getHeaders().size());
+    Assert.assertEquals(response.getHeaders("b"), Arrays.asList("bValue"));
   }
 }