CompressionTests.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*
  2. * Copyright 2020, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import GRPC
  17. import EchoImplementation
  18. import EchoModel
  19. import NIO
  20. import NIOHPACK
  21. import XCTest
  22. class MessageCompressionTests: GRPCTestCase {
  23. var group: EventLoopGroup!
  24. var server: Server!
  25. var client: ClientConnection!
  26. var defaultTimeout: TimeInterval = 0.1
  27. var echo: Echo_EchoClient!
  28. override func setUp() {
  29. self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  30. }
  31. override func tearDown() {
  32. XCTAssertNoThrow(try self.client.close().wait())
  33. XCTAssertNoThrow(try self.server.close().wait())
  34. XCTAssertNoThrow(try self.group.syncShutdownGracefully())
  35. }
  36. func setupServer(encoding: ServerMessageEncoding) throws {
  37. self.server = try Server.insecure(group: self.group)
  38. .withServiceProviders([EchoProvider()])
  39. .withMessageCompression(encoding)
  40. .bind(host: "localhost", port: 0)
  41. .wait()
  42. }
  43. func setupClient(encoding: ClientMessageEncoding) {
  44. self.client = ClientConnection.insecure(group: self.group)
  45. .connect(host: "localhost", port: self.server.channel.localAddress!.port!)
  46. self.echo = Echo_EchoClient(
  47. channel: self.client,
  48. defaultCallOptions: CallOptions(messageEncoding: encoding)
  49. )
  50. }
  51. func doUnaryRPC() -> UnaryCall<Echo_EchoRequest, Echo_EchoResponse> {
  52. let get = self.echo.get(.with { $0.text = "foo" })
  53. return get
  54. }
  55. func testCompressedRequestsUncompressedResponses() throws {
  56. // Enable compression, but don't advertise that it's enabled.
  57. // The spec says that servers should handle compression they support but don't advertise.
  58. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [], decompressionLimit: .ratio(10))))
  59. self.setupClient(encoding: .enabled(.init(forRequests: .gzip, acceptableForResponses: [.deflate, .gzip], decompressionLimit: .ratio(10))))
  60. let get = self.echo.get(.with { $0.text = "foo" })
  61. let initialMetadata = self.expectation(description: "received initial metadata")
  62. get.initialMetadata.map {
  63. $0.contains(name: "grpc-encoding")
  64. }.assertEqual(false, fulfill: initialMetadata)
  65. let status = self.expectation(description: "received status")
  66. get.status.map {
  67. $0.code
  68. }.assertEqual(.ok, fulfill: status)
  69. self.wait(for: [initialMetadata, status], timeout: self.defaultTimeout)
  70. }
  71. func testUncompressedRequestsCompressedResponses() throws {
  72. try self.setupServer(encoding: .enabled(.init(decompressionLimit: .ratio(10))))
  73. self.setupClient(encoding: .enabled(.init(forRequests: .none, acceptableForResponses: [.deflate, .gzip], decompressionLimit: .ratio(10))))
  74. let get = self.echo.get(.with { $0.text = "foo" })
  75. let initialMetadata = self.expectation(description: "received initial metadata")
  76. get.initialMetadata.map {
  77. $0.first(name: "grpc-encoding")
  78. }.assertEqual("deflate", fulfill: initialMetadata)
  79. let status = self.expectation(description: "received status")
  80. get.status.map {
  81. $0.code
  82. }.assertEqual(.ok, fulfill: status)
  83. self.wait(for: [initialMetadata, status], timeout: self.defaultTimeout)
  84. }
  85. func testServerCanDecompressNonAdvertisedButSupportedCompression() throws {
  86. // Server should be able to decompress a format it supports but does not advertise. In doing
  87. // so it must also return a "grpc-accept-encoding" header which includes the value it did not
  88. // advertise.
  89. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [.gzip], decompressionLimit: .ratio(10))))
  90. self.setupClient(encoding: .enabled(.init(forRequests: .deflate, acceptableForResponses: [], decompressionLimit: .ratio(10))))
  91. let get = self.echo.get(.with { $0.text = "foo" })
  92. let initialMetadata = self.expectation(description: "received initial metadata")
  93. get.initialMetadata.map {
  94. $0[canonicalForm: "grpc-accept-encoding"]
  95. }.assertEqual(["gzip", "deflate"], fulfill: initialMetadata)
  96. let status = self.expectation(description: "received status")
  97. get.status.map {
  98. $0.code
  99. }.assertEqual(.ok, fulfill: status)
  100. self.wait(for: [initialMetadata, status], timeout: self.defaultTimeout)
  101. }
  102. func testServerCompressesResponseWithDifferentAlgorithmToRequest() throws {
  103. // Server should be able to compress responses with a different method to the client, providing
  104. // the client supports it.
  105. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [.gzip], decompressionLimit: .ratio(10))))
  106. self.setupClient(encoding: .enabled(.init(forRequests: .deflate, acceptableForResponses: [.deflate, .gzip], decompressionLimit: .ratio(10))))
  107. let get = self.echo.get(.with { $0.text = "foo" })
  108. let initialMetadata = self.expectation(description: "received initial metadata")
  109. get.initialMetadata.map {
  110. $0.first(name: "grpc-encoding")
  111. }.assertEqual("gzip", fulfill: initialMetadata)
  112. let status = self.expectation(description: "received status")
  113. get.status.map {
  114. $0.code
  115. }.assertEqual(.ok, fulfill: status)
  116. self.wait(for: [initialMetadata, status], timeout: self.defaultTimeout)
  117. }
  118. func testCompressedRequestWithCompressionNotSupportedOnServer() throws {
  119. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [.gzip, .deflate], decompressionLimit: .ratio(10))))
  120. // We can't specify a compression we don't support, so we'll specify no compression and then
  121. // send a 'grpc-encoding' with our initial metadata.
  122. self.setupClient(encoding: .enabled(.init(forRequests: .none, acceptableForResponses: [.deflate, .gzip], decompressionLimit: .ratio(10))))
  123. let headers: HPACKHeaders = ["grpc-encoding": "you-don't-support-this"]
  124. let get = self.echo.get(.with { $0.text = "foo" }, callOptions: CallOptions(customMetadata: headers))
  125. let response = self.expectation(description: "received response")
  126. get.response.assertError(fulfill: response)
  127. let trailers = self.expectation(description: "received trailing metadata")
  128. get.trailingMetadata.map {
  129. $0[canonicalForm: "grpc-accept-encoding"]
  130. }.assertEqual(["gzip", "deflate"], fulfill: trailers)
  131. let status = self.expectation(description: "received status")
  132. get.status.map {
  133. $0.code
  134. }.assertEqual(.unimplemented, fulfill: status)
  135. self.wait(for: [response, trailers, status], timeout: self.defaultTimeout)
  136. }
  137. func testDecompressionLimitIsRespectedByServerForUnaryCall() throws {
  138. try self.setupServer(encoding: .enabled(.init(decompressionLimit: .absolute(1))))
  139. self.setupClient(encoding: .enabled(.init(forRequests: .gzip, decompressionLimit: .absolute(1024))))
  140. let get = self.echo.get(.with { $0.text = "foo" })
  141. let status = self.expectation(description: "received status")
  142. get.status.map {
  143. $0.code
  144. }.assertEqual(.resourceExhausted, fulfill: status)
  145. self.wait(for: [status], timeout: self.defaultTimeout)
  146. }
  147. func testDecompressionLimitIsRespectedByServerForStreamingCall() throws {
  148. try self.setupServer(encoding: .enabled(.init(decompressionLimit: .absolute(1024))))
  149. self.setupClient(encoding: .enabled(.init(forRequests: .gzip, decompressionLimit: .absolute(2048))))
  150. let collect = self.echo.collect()
  151. let status = self.expectation(description: "received status")
  152. // Smaller than limit.
  153. collect.sendMessage(.with { $0.text = "foo" }, promise: nil)
  154. // Should be just over the limit.
  155. collect.sendMessage(.with { $0.text = String(repeating: "x", count: 1024)}, promise: nil)
  156. collect.sendEnd(promise: nil)
  157. collect.status.map {
  158. $0.code
  159. }.assertEqual(.resourceExhausted, fulfill: status)
  160. self.wait(for: [status], timeout: self.defaultTimeout)
  161. }
  162. func testDecompressionLimitIsRespectedByClientForUnaryCall() throws {
  163. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [.gzip], decompressionLimit: .absolute(1024))))
  164. self.setupClient(encoding: .enabled(.responsesOnly(decompressionLimit: .absolute(1))))
  165. let get = self.echo.get(.with { $0.text = "foo" })
  166. let status = self.expectation(description: "received status")
  167. get.status.map {
  168. $0.code
  169. }.assertEqual(.resourceExhausted, fulfill: status)
  170. self.wait(for: [status], timeout: self.defaultTimeout)
  171. }
  172. func testDecompressionLimitIsRespectedByClientForStreamingCall() throws {
  173. try self.setupServer(encoding: .enabled(.init(decompressionLimit: .absolute(2048))))
  174. self.setupClient(encoding: .enabled(.init(forRequests: .gzip, decompressionLimit: .absolute(1024))))
  175. var responses: [Echo_EchoResponse] = []
  176. let update = self.echo.update {
  177. responses.append($0)
  178. }
  179. let status = self.expectation(description: "received status")
  180. // Smaller than limit.
  181. update.sendMessage(.with { $0.text = "foo" }, promise: nil)
  182. // Should be just over the limit.
  183. update.sendMessage(.with { $0.text = String(repeating: "x", count: 1024)}, promise: nil)
  184. update.sendEnd(promise: nil)
  185. update.status.map {
  186. $0.code
  187. }.assertEqual(.resourceExhausted, fulfill: status)
  188. self.wait(for: [status], timeout: self.defaultTimeout)
  189. XCTAssertEqual(responses.count, 1)
  190. }
  191. }