Supports x-accept-content-transfer-encoding: base64 and
x-accept-response-streaming: true.
diff --git a/init_submodules.sh b/init_submodules.sh
index 425d443..8a4a6fd 100755
--- a/init_submodules.sh
+++ b/init_submodules.sh
@@ -1,4 +1,4 @@
 git submodule update --init
 cd third_party/closure-library && git checkout tags/v20160911 -f && cd ../..
 cd third_party/openssl && git checkout tags/OpenSSL_1_0_2h -f && cd ../..
-cd third_party/grpc && git checkout master -f && git submodule update --init && cd ../..
+cd third_party/grpc && git checkout tags/v1.1.4 -f && git submodule update --init && cd ../..
diff --git a/net/grpc/gateway/codec/b64_proto_encoder.cc b/net/grpc/gateway/codec/b64_proto_encoder.cc
new file mode 100644
index 0000000..1dcf830
--- /dev/null
+++ b/net/grpc/gateway/codec/b64_proto_encoder.cc
@@ -0,0 +1,26 @@
+#include "net/grpc/gateway/codec/b64_proto_encoder.h"
+
+namespace grpc {
+namespace gateway {
+
+B64ProtoEncoder::B64ProtoEncoder() {}
+
+B64ProtoEncoder::~B64ProtoEncoder() {}
+
+void B64ProtoEncoder::Encode(grpc::ByteBuffer* input,
+                             std::vector<Slice>* result) {
+  std::vector<Slice> buffer;
+  ProtoEncoder::Encode(input, &buffer);
+  base64_.Encode(buffer, result);
+}
+
+void B64ProtoEncoder::EncodeStatus(const grpc::Status& status,
+                                   const Trailers* trailers,
+                                   std::vector<Slice>* result) {
+  std::vector<Slice> buffer;
+  ProtoEncoder::EncodeStatus(status, trailers, &buffer);
+  base64_.Encode(buffer, result);
+}
+
+}  // namespace gateway
+}  // namespace grpc
diff --git a/net/grpc/gateway/codec/b64_proto_encoder.h b/net/grpc/gateway/codec/b64_proto_encoder.h
new file mode 100644
index 0000000..01653e0
--- /dev/null
+++ b/net/grpc/gateway/codec/b64_proto_encoder.h
@@ -0,0 +1,29 @@
+#ifndef NET_GRPC_GATEWAY_CODEC_B64_PROTO_ENCODER_H_
+#define NET_GRPC_GATEWAY_CODEC_B64_PROTO_ENCODER_H_
+
+#include "net/grpc/gateway/codec/base64.h"
+#include "net/grpc/gateway/codec/proto_encoder.h"
+
+namespace grpc {
+namespace gateway {
+
+class B64ProtoEncoder : public ProtoEncoder {
+ public:
+  B64ProtoEncoder();
+  ~B64ProtoEncoder() override;
+
+  // B64ProtoEncoder is neither copyable nor movable.
+  B64ProtoEncoder(const B64ProtoEncoder&) = delete;
+  B64ProtoEncoder& operator=(const B64ProtoEncoder&) = delete;
+
+  void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) override;
+  void EncodeStatus(const grpc::Status& status, const Trailers* trailers,
+                    std::vector<Slice>* result) override;
+
+ private:
+  Base64 base64_;
+};
+
+}  // namespace gateway
+}  // namespace grpc
+#endif  // NET_GRPC_GATEWAY_CODEC_B64_PROTO_ENCODER_H_
diff --git a/net/grpc/gateway/codec/proto_encoder.cc b/net/grpc/gateway/codec/proto_encoder.cc
new file mode 100644
index 0000000..5387597
--- /dev/null
+++ b/net/grpc/gateway/codec/proto_encoder.cc
@@ -0,0 +1,50 @@
+#include "net/grpc/gateway/codec/proto_encoder.h"
+
+#include <utility>
+#include <vector>
+
+#include "google/protobuf/any.pb.h"
+#include "net/grpc/gateway/protos/pair.pb.h"
+#include "net/grpc/gateway/protos/status.pb.h"
+#include "net/grpc/gateway/runtime/constants.h"
+#include "net/grpc/gateway/runtime/types.h"
+#include "third_party/grpc/include/grpc++/support/byte_buffer.h"
+#include "third_party/grpc/include/grpc++/support/slice.h"
+#include "third_party/grpc/include/grpc/slice.h"
+
+namespace grpc {
+namespace gateway {
+
+ProtoEncoder::ProtoEncoder() {}
+
+ProtoEncoder::~ProtoEncoder() {}
+
+void ProtoEncoder::Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) {
+  input->Dump(result);
+}
+
+void ProtoEncoder::EncodeStatus(const grpc::Status& status,
+                                const Trailers* trailers,
+                                std::vector<Slice>* result) {
+  google::rpc::Status status_proto;
+  status_proto.set_code(status.error_code());
+  status_proto.set_message(status.error_message());
+  if (trailers != nullptr) {
+    for (auto& trailer : *trailers) {
+      ::google::protobuf::Any* any = status_proto.add_details();
+      any->set_type_url(kTypeUrlPair);
+      Pair pair;
+      pair.set_first(trailer.first);
+      pair.set_second(trailer.second.data(), trailer.second.length());
+      pair.SerializeToString(any->mutable_value());
+    }
+  }
+
+  grpc_slice status_slice = grpc_slice_malloc(status_proto.ByteSizeLong());
+  status_proto.SerializeToArray(GPR_SLICE_START_PTR(status_slice),
+                                GPR_SLICE_LENGTH(status_slice));
+  result->push_back(Slice(status_slice, Slice::STEAL_REF));
+}
+
+}  // namespace gateway
+}  // namespace grpc
diff --git a/net/grpc/gateway/codec/proto_encoder.h b/net/grpc/gateway/codec/proto_encoder.h
new file mode 100644
index 0000000..61dee80
--- /dev/null
+++ b/net/grpc/gateway/codec/proto_encoder.h
@@ -0,0 +1,26 @@
+#ifndef NET_GRPC_GATEWAY_CODEC_PROTO_ENCODER_H_
+#define NET_GRPC_GATEWAY_CODEC_PROTO_ENCODER_H_
+
+#include "net/grpc/gateway/codec/encoder.h"
+
+namespace grpc {
+namespace gateway {
+
+class ProtoEncoder : public Encoder {
+ public:
+  ProtoEncoder();
+  ~ProtoEncoder() override;
+
+  // ProtoEncoder is neither copyable nor movable.
+  ProtoEncoder(const ProtoEncoder&) = delete;
+  ProtoEncoder& operator=(const ProtoEncoder&) = delete;
+
+  void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) override;
+
+  void EncodeStatus(const grpc::Status& status, const Trailers* trailers,
+                    std::vector<Slice>* result) override;
+};
+
+}  // namespace gateway
+}  // namespace grpc
+#endif  // NET_GRPC_GATEWAY_CODEC_PROTO_ENCODER_H_
diff --git a/net/grpc/gateway/frontend/nginx_http_frontend.cc b/net/grpc/gateway/frontend/nginx_http_frontend.cc
index 462ec3b..4ff6bb3 100644
--- a/net/grpc/gateway/frontend/nginx_http_frontend.cc
+++ b/net/grpc/gateway/frontend/nginx_http_frontend.cc
@@ -113,7 +113,8 @@
 NginxHttpFrontend::NginxHttpFrontend(std::unique_ptr<Backend> backend)
     : Frontend(std::move(backend)),
       http_request_(nullptr),
-      protocol_(UNKNOWN),
+      request_protocol_(UNKNOWN),
+      response_protocol_(UNKNOWN),
       is_request_half_closed_(false),
       is_request_half_closed_sent_(false),
       is_request_init_metadata_sent_(false),
@@ -131,7 +132,8 @@
 
   backend()->Start();
   // Enable request streaming.
-  if (protocol_ == Protocol::B64_PROTO || protocol_ == Protocol::PROTO) {
+  if (request_protocol_ == Protocol::B64_PROTO ||
+      request_protocol_ == Protocol::PROTO) {
     http_request_->request_body_no_buffering = false;
   } else {
     http_request_->request_body_no_buffering = true;
@@ -263,7 +265,7 @@
   is_response_status_sent_ = true;
 
   std::vector<Slice> trancoded_status;
-  if (protocol_ == Protocol::GRPC) {
+  if (request_protocol_ == Protocol::GRPC) {
     SendResponseTrailersToClient(response);
   } else {
     encoder_->EncodeStatus(*response->status(), response->trailers(),
@@ -464,7 +466,7 @@
       AddHTTPHeader(http_request_, header.first, header.second);
     }
   }
-  switch (protocol_) {
+  switch (response_protocol_) {
     case GRPC:
       AddHTTPHeader(http_request_, kGrpcEncoding, kGrpcEncoding_Identity);
       AddHTTPHeader(http_request_, kContentType, kContentTypeGrpc);
@@ -478,11 +480,17 @@
       AddHTTPHeader(http_request_, kContentType, kContentTypeJson);
       break;
     case PROTO:
+      AddHTTPHeader(http_request_, kContentType, kContentTypeProto);
+      break;
     case PROTO_STREAM_BODY:
       AddHTTPHeader(http_request_, kContentType, kContentTypeStreamBody);
       break;
     case B64_PROTO:
-    case B64_STREAM_BODY:
+      AddHTTPHeader(http_request_, kContentType, kContentTypeProto);
+      AddHTTPHeader(http_request_, kContentTransferEncoding,
+                    kContentTransferEncoding_Base64);
+      break;
+    case B64_PROTO_STREAM_BODY:
       AddHTTPHeader(http_request_, kContentType, kContentTypeStreamBody);
       AddHTTPHeader(http_request_, kContentTransferEncoding,
                     kContentTransferEncoding_Base64);
diff --git a/net/grpc/gateway/frontend/nginx_http_frontend.h b/net/grpc/gateway/frontend/nginx_http_frontend.h
index 62af2d8..dace3a7 100644
--- a/net/grpc/gateway/frontend/nginx_http_frontend.h
+++ b/net/grpc/gateway/frontend/nginx_http_frontend.h
@@ -66,7 +66,11 @@
     decoder_ = std::move(decoder);
   }
 
-  void set_protocol(Protocol protocol) { protocol_ = protocol; }
+  void set_request_protocol(Protocol protocol) { request_protocol_ = protocol; }
+
+  void set_response_protocol(Protocol protocol) {
+    response_protocol_ = protocol;
+  }
 
  private:
   friend void ::continue_read_request_body(ngx_http_request_t *r);
@@ -105,7 +109,9 @@
   std::unique_ptr<Decoder> decoder_;
   std::unique_ptr<Encoder> encoder_;
   // The frontend protocol of the current processing request.
-  Protocol protocol_;
+  Protocol request_protocol_;
+  // The frontend protocol of the current processing response.
+  Protocol response_protocol_;
   // True if already reach the end of the HTTP request from nginx.
   bool is_request_half_closed_;
   // True if the half close has been sent to the GRPC backend.
diff --git a/net/grpc/gateway/runtime/constants.h b/net/grpc/gateway/runtime/constants.h
index 4418416..1a3fc7d 100644
--- a/net/grpc/gateway/runtime/constants.h
+++ b/net/grpc/gateway/runtime/constants.h
@@ -78,11 +78,15 @@
 const size_t kXAcceptContentTransferEncodingLength =
     sizeof(kXAcceptContentTransferEncoding) - 1;
 const char kXAcceptContentTransferEncoding_Base64[] = "base64";
+const size_t kXAcceptContentTransferEncoding_Base64_Length =
+    sizeof(kXAcceptContentTransferEncoding_Base64) - 1;
 
 const char kXAcceptResponseStreaming[] = "x-accept-response-streaming";
 const size_t kXAcceptResponseStreamingLength =
     sizeof(kXAcceptResponseStreaming) - 1;
 const char kXAcceptResponseStreaming_True[] = "true";
+const size_t kXAcceptResponseStreaming_True_Length =
+    sizeof(kXAcceptResponseStreaming_True) - 1;
 
 // The frontend protocols supported by GRPC-Web gateway.
 enum Protocol {
@@ -91,7 +95,7 @@
   GRPC_WEB,
   JSON_STREAM_BODY,
   PROTO_STREAM_BODY,
-  B64_STREAM_BODY,
+  B64_PROTO_STREAM_BODY,
   PROTO,
   B64_PROTO,
 };
diff --git a/net/grpc/gateway/runtime/runtime.cc b/net/grpc/gateway/runtime/runtime.cc
index 4cde901..fb45b59 100644
--- a/net/grpc/gateway/runtime/runtime.cc
+++ b/net/grpc/gateway/runtime/runtime.cc
@@ -6,6 +6,7 @@
 
 #include "net/grpc/gateway/backend/grpc_backend.h"
 #include "net/grpc/gateway/codec/b64_proto_decoder.h"
+#include "net/grpc/gateway/codec/b64_proto_encoder.h"
 #include "net/grpc/gateway/codec/b64_stream_body_decoder.h"
 #include "net/grpc/gateway/codec/b64_stream_body_encoder.h"
 #include "net/grpc/gateway/codec/grpc_decoder.h"
@@ -15,6 +16,7 @@
 #include "net/grpc/gateway/codec/json_decoder.h"
 #include "net/grpc/gateway/codec/json_encoder.h"
 #include "net/grpc/gateway/codec/proto_decoder.h"
+#include "net/grpc/gateway/codec/proto_encoder.h"
 #include "net/grpc/gateway/codec/stream_body_decoder.h"
 #include "net/grpc/gateway/codec/stream_body_encoder.h"
 #include "net/grpc/gateway/frontend/nginx_http_frontend.h"
@@ -37,6 +39,29 @@
   }
   return string_ref();
 }
+
+bool IsResponseStreaming(ngx_http_request_t* http_request) {
+  string_ref value = GetHTTPHeader(&http_request->headers_in.headers.part,
+                                   kXAcceptResponseStreaming);
+  if (kXAcceptResponseStreaming_True_Length == value.size() &&
+      strncasecmp(kXAcceptResponseStreaming_True, value.data(), value.size()) ==
+          0) {
+    return true;
+  }
+  return false;
+}
+
+bool IsResponseB64(ngx_http_request_t* http_request) {
+  string_ref value = GetHTTPHeader(&http_request->headers_in.headers.part,
+                                   kXAcceptContentTransferEncoding);
+  if (kXAcceptContentTransferEncoding_Base64_Length == value.size() &&
+      strncasecmp(kXAcceptContentTransferEncoding_Base64, value.data(),
+                  value.size()) == 0) {
+    return true;
+  }
+  return false;
+}
+
 }  // namespace
 
 Runtime::Runtime() {
@@ -69,126 +94,74 @@
   }
   NginxHttpFrontend* frontend = new NginxHttpFrontend(std::move(backend));
   frontend->set_http_request(http_request);
-  frontend->set_encoder(CreateEncoder(http_request));
-  frontend->set_decoder(CreateDecoder(http_request));
-  frontend->set_protocol(DetectFrontendProtocol(http_request));
+  Protocol request_protocol = DetectRequestProtocol(http_request);
+  frontend->set_request_protocol(request_protocol);
+  frontend->set_decoder(CreateDecoder(request_protocol, http_request));
+  Protocol response_protocol = DetectResponseProtocol(http_request);
+  frontend->set_response_protocol(response_protocol);
+  frontend->set_encoder(CreateEncoder(response_protocol, http_request));
+
   return std::shared_ptr<Frontend>(frontend);
 }
 
 std::unique_ptr<Encoder> Runtime::CreateEncoder(
-    ngx_http_request_t* http_request) {
-  if (http_request == nullptr ||
-      http_request->headers_in.content_type == nullptr) {
-    return nullptr;
-  }
-  const char* content_type = reinterpret_cast<const char*>(
-      http_request->headers_in.content_type->value.data);
-  size_t content_type_length = http_request->headers_in.content_type->value.len;
-  if (content_type_length == kContentTypeGrpcWebLength &&
-      strncasecmp(kContentTypeGrpcWeb, content_type, kContentTypeGrpcLength) ==
-          0) {
-    return std::unique_ptr<Encoder>(new GrpcWebEncoder());
-  }
-  if (content_type_length == kContentTypeStreamBodyLength &&
-      strncasecmp(kContentTypeStreamBody, content_type,
-                  kContentTypeStreamBodyLength) == 0) {
-    string_ref value = GetHTTPHeader(&http_request->headers_in.headers.part,
-                                     kContentTransferEncoding);
-    if (kContentTransferEncoding_Base64_Length == value.size() &&
-        strncasecmp(kContentTransferEncoding_Base64, value.data(),
-                    value.size()) == 0) {
+    Protocol protocol, ngx_http_request_t* http_request) {
+  switch (protocol) {
+    case GRPC:
+      return std::unique_ptr<Encoder>(new GrpcEncoder());
+    case GRPC_WEB:
+      return std::unique_ptr<Encoder>(new GrpcWebEncoder());
+    case JSON_STREAM_BODY:
+      return std::unique_ptr<Encoder>(new JsonEncoder());
+    case PROTO_STREAM_BODY:
+      return std::unique_ptr<Encoder>(new StreamBodyEncoder());
+    case B64_PROTO_STREAM_BODY:
       return std::unique_ptr<Encoder>(new B64StreamBodyEncoder());
-    }
-    return std::unique_ptr<Encoder>(new StreamBodyEncoder());
+    case PROTO:
+      return std::unique_ptr<Encoder>(new ProtoEncoder());
+    case B64_PROTO:
+      return std::unique_ptr<Encoder>(new B64ProtoEncoder());
+    default:
+      return nullptr;
   }
-  if (content_type_length == kContentTypeProtoLength &&
-      strncasecmp(kContentTypeProto, content_type, kContentTypeProtoLength) ==
-          0) {
-    string_ref value = GetHTTPHeader(&http_request->headers_in.headers.part,
-                                     kContentTransferEncoding);
-    if (kContentTransferEncoding_Base64_Length == value.size() &&
-        strncasecmp(kContentTransferEncoding_Base64, value.data(),
-                    value.size()) == 0) {
-      return std::unique_ptr<Encoder>(new B64StreamBodyEncoder());
-    }
-    return std::unique_ptr<Encoder>(new StreamBodyEncoder());
-  }
-  if (content_type_length == kContentTypeJsonLength &&
-      strncasecmp(kContentTypeJson, content_type, kContentTypeJsonLength) ==
-          0) {
-    return std::unique_ptr<Encoder>(new JsonEncoder());
-  }
-  if (content_type_length == kContentTypeGrpcLength &&
-      strncasecmp(kContentTypeGrpc, content_type, kContentTypeGrpcLength) ==
-          0) {
-    return std::unique_ptr<Encoder>(new GrpcEncoder());
-  }
-  return nullptr;
 }
 
 std::unique_ptr<Decoder> Runtime::CreateDecoder(
-    ngx_http_request_t* http_request) {
-  if (http_request == nullptr ||
-      http_request->headers_in.content_type == nullptr) {
-    return nullptr;
-  }
-  const char* content_type = reinterpret_cast<const char*>(
-      http_request->headers_in.content_type->value.data);
-  size_t content_type_length = http_request->headers_in.content_type->value.len;
-  if (content_type_length == kContentTypeGrpcWebLength &&
-      strncasecmp(kContentTypeGrpcWeb, content_type, kContentTypeGrpcLength) ==
-          0) {
-    return std::unique_ptr<Decoder>(new GrpcWebDecoder());
-  }
-  if (content_type_length == kContentTypeProtoLength &&
-      strncasecmp(kContentTypeProto, content_type, kContentTypeProtoLength) ==
-          0) {
-    string_ref value = GetHTTPHeader(&http_request->headers_in.headers.part,
-                                     kContentTransferEncoding);
-    if (kContentTransferEncoding_Base64_Length == value.size() &&
-        strncasecmp(kContentTransferEncoding_Base64, value.data(),
-                    value.size()) == 0) {
-      return std::unique_ptr<Decoder>(new B64ProtoDecoder());
+    Protocol protocol, ngx_http_request_t* http_request) {
+  switch (protocol) {
+    case GRPC: {
+      GrpcDecoder* decoder = new GrpcDecoder();
+      string_ref value =
+          GetHTTPHeader(&http_request->headers_in.headers.part, kGrpcEncoding);
+      if (kGrpcEncoding_Identity_Length == value.size() &&
+          strncasecmp(kGrpcEncoding_Identity, value.data(), value.size()) ==
+              0) {
+        decoder->set_compression_algorithm(GrpcDecoder::kIdentity);
+      } else if (kGrpcEncoding_Gzip_Length == value.size() &&
+                 strncasecmp(kGrpcEncoding_Gzip, value.data(), value.size()) ==
+                     0) {
+        decoder->set_compression_algorithm(GrpcDecoder::kGzip);
+      }
+      return std::unique_ptr<Decoder>(decoder);
     }
-    return std::unique_ptr<Decoder>(new ProtoDecoder());
-  }
-  if (content_type_length == kContentTypeStreamBodyLength &&
-      strncasecmp(kContentTypeStreamBody, content_type,
-                  kContentTypeStreamBodyLength) == 0) {
-    string_ref value = GetHTTPHeader(&http_request->headers_in.headers.part,
-                                     kContentTransferEncoding);
-    if (kContentTransferEncoding_Base64_Length == value.size() &&
-        strncasecmp(kContentTransferEncoding_Base64, value.data(),
-                    value.size()) == 0) {
+    case GRPC_WEB:
+      return std::unique_ptr<Decoder>(new GrpcWebDecoder());
+    case JSON_STREAM_BODY:
+      return std::unique_ptr<Decoder>(new JsonDecoder());
+    case PROTO_STREAM_BODY:
+      return std::unique_ptr<Decoder>(new StreamBodyDecoder());
+    case B64_PROTO_STREAM_BODY:
       return std::unique_ptr<Decoder>(new B64StreamBodyDecoder());
-    }
-    return std::unique_ptr<Decoder>(new StreamBodyDecoder());
+    case PROTO:
+      return std::unique_ptr<Decoder>(new ProtoDecoder());
+    case B64_PROTO:
+      return std::unique_ptr<Decoder>(new B64ProtoDecoder());
+    default:
+      return nullptr;
   }
-  if (content_type_length == kContentTypeJsonLength &&
-      strncasecmp(kContentTypeJson, content_type, kContentTypeJsonLength) ==
-          0) {
-    return std::unique_ptr<Decoder>(new JsonDecoder());
-  }
-  if (content_type_length == kContentTypeGrpcLength &&
-      strncasecmp(kContentTypeGrpc, content_type, kContentTypeGrpcLength) ==
-          0) {
-    GrpcDecoder* decoder = new GrpcDecoder();
-    string_ref value =
-        GetHTTPHeader(&http_request->headers_in.headers.part, kGrpcEncoding);
-    if (kGrpcEncoding_Identity_Length == value.size() &&
-        strncasecmp(kGrpcEncoding_Identity, value.data(), value.size()) == 0) {
-      decoder->set_compression_algorithm(GrpcDecoder::kIdentity);
-    } else if (kGrpcEncoding_Gzip_Length == value.size() &&
-               strncasecmp(kGrpcEncoding_Gzip, value.data(), value.size()) ==
-                   0) {
-      decoder->set_compression_algorithm(GrpcDecoder::kGzip);
-    }
-    return std::unique_ptr<Decoder>(decoder);
-  }
-  return nullptr;
 }
 
-Protocol Runtime::DetectFrontendProtocol(ngx_http_request_t* http_request) {
+Protocol Runtime::DetectRequestProtocol(ngx_http_request_t* http_request) {
   if (http_request == nullptr ||
       http_request->headers_in.content_type == nullptr) {
     return UNKNOWN;
@@ -216,7 +189,7 @@
     if (kContentTransferEncoding_Base64_Length == value.size() &&
         strncasecmp(kContentTransferEncoding_Base64, value.data(),
                     value.size()) == 0) {
-      return B64_STREAM_BODY;
+      return B64_PROTO_STREAM_BODY;
     }
     return PROTO_STREAM_BODY;
   }
@@ -238,6 +211,50 @@
   return UNKNOWN;
 }
 
+Protocol Runtime::DetectResponseProtocol(ngx_http_request_t* http_request) {
+  if (http_request == nullptr ||
+      http_request->headers_in.content_type == nullptr) {
+    return UNKNOWN;
+  }
+  const char* content_type = reinterpret_cast<const char*>(
+      http_request->headers_in.content_type->value.data);
+  size_t content_type_length = http_request->headers_in.content_type->value.len;
+  if ((content_type_length == kContentTypeProtoLength &&
+       strncasecmp(kContentTypeProto, content_type, kContentTypeProtoLength) ==
+           0) ||
+      (content_type_length == kContentTypeStreamBodyLength &&
+       strncasecmp(kContentTypeStreamBody, content_type,
+                   kContentTypeStreamBodyLength) == 0)) {
+    if (IsResponseStreaming(http_request)) {
+      if (IsResponseB64(http_request)) {
+        return B64_PROTO_STREAM_BODY;
+      }
+      return PROTO_STREAM_BODY;
+    } else {
+      if (IsResponseB64(http_request)) {
+        return B64_PROTO;
+      }
+      return PROTO;
+    }
+  }
+  if (content_type_length == kContentTypeJsonLength &&
+      strncasecmp(kContentTypeJson, content_type, kContentTypeJsonLength) ==
+          0) {
+    return JSON_STREAM_BODY;
+  }
+  if (content_type_length == kContentTypeGrpcLength &&
+      strncasecmp(kContentTypeGrpc, content_type, kContentTypeGrpcLength) ==
+          0) {
+    return GRPC;
+  }
+  if (content_type_length == kContentTypeGrpcWebLength &&
+      strncasecmp(kContentTypeGrpcWeb, content_type,
+                  kContentTypeGrpcWebLength) == 0) {
+    return GRPC_WEB;
+  }
+  return UNKNOWN;
+}
+
 grpc_channel* Runtime::GetBackendChannel(const std::string& backend_address,
                                          bool use_shared_channel_pool) {
   if (use_shared_channel_pool) {
diff --git a/net/grpc/gateway/runtime/runtime.h b/net/grpc/gateway/runtime/runtime.h
index 4bdf631..8f82267 100644
--- a/net/grpc/gateway/runtime/runtime.h
+++ b/net/grpc/gateway/runtime/runtime.h
@@ -56,14 +56,19 @@
 
   // Creates an encoder for the given content type. This method doesn't take the
   // ownership of http_request parameter and it cannot be nullptr.
-  std::unique_ptr<Encoder> CreateEncoder(ngx_http_request_t *http_request);
+  std::unique_ptr<Encoder> CreateEncoder(Protocol protocol,
+                                         ngx_http_request_t *http_request);
 
   // Creates a decoder for the given content type. This method doesn't take the
   // ownership of http_request parameter and it cannot be nullptr.
-  std::unique_ptr<Decoder> CreateDecoder(ngx_http_request_t *http_request);
+  std::unique_ptr<Decoder> CreateDecoder(Protocol protocol,
+                                         ngx_http_request_t *http_request);
 
-  // Detects the frontend protocol.
-  Protocol DetectFrontendProtocol(ngx_http_request_t *http_request);
+  // Detects the frontend request protocol.
+  Protocol DetectRequestProtocol(ngx_http_request_t *http_request);
+
+  // Detects the frontend response protocol.
+  Protocol DetectResponseProtocol(ngx_http_request_t *http_request);
 
   std::unique_ptr<GrpcEventQueue> grpc_event_queue_;