|
@@ -87,7 +87,7 @@ public final class HTTP1ToGRPCServerCodec {
|
|
|
// TODO(kaipi): Extract all gRPC Web processing logic into an independent handler only added on
|
|
// TODO(kaipi): Extract all gRPC Web processing logic into an independent handler only added on
|
|
|
// the HTTP1.1 pipeline, as it's starting to get in the way of readability.
|
|
// the HTTP1.1 pipeline, as it's starting to get in the way of readability.
|
|
|
private var requestTextBuffer: NIO.ByteBuffer!
|
|
private var requestTextBuffer: NIO.ByteBuffer!
|
|
|
- private var responseTextBuffer: NIO.ByteBuffer!
|
|
|
|
|
|
|
+ private var responseTextBuffers: CircularBuffer<ByteBuffer> = []
|
|
|
|
|
|
|
|
var inboundState = InboundState.expectingHeaders {
|
|
var inboundState = InboundState.expectingHeaders {
|
|
|
willSet {
|
|
willSet {
|
|
@@ -311,10 +311,6 @@ extension HTTP1ToGRPCServerCodec: ChannelOutboundHandler {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if self.contentType == .webTextProtobuf {
|
|
|
|
|
- responseTextBuffer = context.channel.allocator.buffer(capacity: 0)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
// Are we compressing responses?
|
|
// Are we compressing responses?
|
|
|
if let responseEncoding = self.responseEncodingHeader {
|
|
if let responseEncoding = self.responseEncodingHeader {
|
|
|
headers.add(name: GRPCHeaderName.encoding, value: responseEncoding)
|
|
headers.add(name: GRPCHeaderName.encoding, value: responseEncoding)
|
|
@@ -340,12 +336,12 @@ extension HTTP1ToGRPCServerCodec: ChannelOutboundHandler {
|
|
|
// Store the response into an independent buffer. We can't return the message directly as
|
|
// Store the response into an independent buffer. We can't return the message directly as
|
|
|
// it needs to be aggregated with all the responses plus the trailers, in order to have
|
|
// it needs to be aggregated with all the responses plus the trailers, in order to have
|
|
|
// the base64 response properly encoded in a single byte stream.
|
|
// the base64 response properly encoded in a single byte stream.
|
|
|
- precondition(self.responseTextBuffer != nil)
|
|
|
|
|
- try self.messageWriter.write(
|
|
|
|
|
|
|
+ let buffer = try self.messageWriter.write(
|
|
|
buffer: messageContext.message,
|
|
buffer: messageContext.message,
|
|
|
- into: &self.responseTextBuffer,
|
|
|
|
|
|
|
+ allocator: context.channel.allocator,
|
|
|
compressed: messageContext.compressed
|
|
compressed: messageContext.compressed
|
|
|
)
|
|
)
|
|
|
|
|
+ self.responseTextBuffers.append(buffer)
|
|
|
|
|
|
|
|
// Since we stored the written data, mark the write promise as successful so that the
|
|
// Since we stored the written data, mark the write promise as successful so that the
|
|
|
// ServerStreaming provider continues sending the data.
|
|
// ServerStreaming provider continues sending the data.
|
|
@@ -383,25 +379,38 @@ extension HTTP1ToGRPCServerCodec: ChannelOutboundHandler {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if contentType == .webTextProtobuf {
|
|
if contentType == .webTextProtobuf {
|
|
|
- precondition(responseTextBuffer != nil)
|
|
|
|
|
-
|
|
|
|
|
// Encode the trailers into the response byte stream as a length delimited message, as per
|
|
// Encode the trailers into the response byte stream as a length delimited message, as per
|
|
|
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
|
|
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
|
|
|
let textTrailers = trailers.map { name, value in "\(name): \(value)" }.joined(separator: "\r\n")
|
|
let textTrailers = trailers.map { name, value in "\(name): \(value)" }.joined(separator: "\r\n")
|
|
|
- responseTextBuffer.writeInteger(UInt8(0x80))
|
|
|
|
|
- responseTextBuffer.writeInteger(UInt32(textTrailers.utf8.count))
|
|
|
|
|
- responseTextBuffer.writeString(textTrailers)
|
|
|
|
|
|
|
+ var trailersBuffer = context.channel.allocator.buffer(capacity: 5 + textTrailers.utf8.count)
|
|
|
|
|
+ trailersBuffer.writeInteger(UInt8(0x80))
|
|
|
|
|
+ trailersBuffer.writeInteger(UInt32(textTrailers.utf8.count))
|
|
|
|
|
+ trailersBuffer.writeString(textTrailers)
|
|
|
|
|
+ self.responseTextBuffers.append(trailersBuffer)
|
|
|
|
|
+
|
|
|
|
|
+ // The '!' is fine, we know it's not empty since we just added a buffer.
|
|
|
|
|
+ var responseTextBuffer = self.responseTextBuffers.popFirst()!
|
|
|
|
|
+
|
|
|
|
|
+ // Read the data from the first buffer.
|
|
|
|
|
+ var accumulatedData = responseTextBuffer.readData(length: responseTextBuffer.readableBytes)!
|
|
|
|
|
+
|
|
|
|
|
+ // Reserve enough capacity and append the remaining buffers.
|
|
|
|
|
+ let requiredExtraCapacity = self.responseTextBuffers.lazy.map { $0.readableBytes }.reduce(0, +)
|
|
|
|
|
+ accumulatedData.reserveCapacity(accumulatedData.count + requiredExtraCapacity)
|
|
|
|
|
+ while let buffer = self.responseTextBuffers.popFirst() {
|
|
|
|
|
+ accumulatedData.append(contentsOf: buffer.readableBytesView)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// TODO: Binary responses that are non multiples of 3 will end = or == when encoded in
|
|
// TODO: Binary responses that are non multiples of 3 will end = or == when encoded in
|
|
|
// base64. Investigate whether this might have any effect on the transport mechanism and
|
|
// base64. Investigate whether this might have any effect on the transport mechanism and
|
|
|
- // client decoding. Initial results say that they are inocuous, but we might have to keep
|
|
|
|
|
|
|
+ // client decoding. Initial results say that they are innocuous, but we might have to keep
|
|
|
// an eye on this in case something trips up.
|
|
// an eye on this in case something trips up.
|
|
|
- if let binaryData = responseTextBuffer.readData(length: responseTextBuffer.readableBytes) {
|
|
|
|
|
- let encodedData = binaryData.base64EncodedString()
|
|
|
|
|
- responseTextBuffer.clear()
|
|
|
|
|
- responseTextBuffer.reserveCapacity(encodedData.utf8.count)
|
|
|
|
|
- responseTextBuffer.writeString(encodedData)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let encodedData = accumulatedData.base64EncodedString()
|
|
|
|
|
+
|
|
|
|
|
+ // Reuse our first buffer.
|
|
|
|
|
+ responseTextBuffer.clear(minimumCapacity: numericCast(encodedData.utf8.count))
|
|
|
|
|
+ responseTextBuffer.writeString(encodedData)
|
|
|
|
|
+
|
|
|
// After collecting all response for gRPC Web connections, send one final aggregated
|
|
// After collecting all response for gRPC Web connections, send one final aggregated
|
|
|
// response.
|
|
// response.
|
|
|
context.write(self.wrapOutboundOut(.body(.byteBuffer(responseTextBuffer))), promise: promise)
|
|
context.write(self.wrapOutboundOut(.body(.byteBuffer(responseTextBuffer))), promise: promise)
|