CompressionTests.swift 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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_EchoServiceClient!
  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. let configuration = Server.Configuration(
  38. target: .hostAndPort("localhost", 0),
  39. eventLoopGroup: self.group,
  40. serviceProviders: [EchoProvider()],
  41. messageEncoding: encoding
  42. )
  43. self.server = try Server.start(configuration: configuration).wait()
  44. }
  45. func setupClient(encoding: ClientMessageEncoding) {
  46. let configuration = ClientConnection.Configuration(
  47. target: .hostAndPort("localhost", self.server.channel.localAddress!.port!),
  48. eventLoopGroup: self.group
  49. )
  50. self.client = ClientConnection(configuration: configuration)
  51. self.echo = Echo_EchoServiceClient(
  52. channel: self.client,
  53. defaultCallOptions: CallOptions(messageEncoding: encoding)
  54. )
  55. }
  56. func doUnaryRPC() -> UnaryCall<Echo_EchoRequest, Echo_EchoResponse> {
  57. let get = self.echo.get(.with { $0.text = "foo" })
  58. return get
  59. }
  60. func testCompressedRequestsUncompressedResponses() throws {
  61. // Enable compression, but don't advertise that it's enabled.
  62. // The spec says that servers should handle compression they support but don't advertise.
  63. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [], decompressionLimit: .ratio(10))))
  64. self.setupClient(encoding: .enabled(.init(forRequests: .gzip, acceptableForResponses: [.deflate, .gzip], decompressionLimit: .ratio(10))))
  65. let get = self.echo.get(.with { $0.text = "foo" })
  66. let initialMetadata = self.expectation(description: "received initial metadata")
  67. get.initialMetadata.map {
  68. $0.contains(name: "grpc-encoding")
  69. }.assertEqual(false, fulfill: initialMetadata)
  70. let status = self.expectation(description: "received status")
  71. get.status.map {
  72. $0.code
  73. }.assertEqual(.ok, fulfill: status)
  74. self.wait(for: [initialMetadata, status], timeout: self.defaultTimeout)
  75. }
  76. func testUncompressedRequestsCompressedResponses() throws {
  77. try self.setupServer(encoding: .enabled(.init(decompressionLimit: .ratio(10))))
  78. self.setupClient(encoding: .enabled(.init(forRequests: .none, acceptableForResponses: [.deflate, .gzip], decompressionLimit: .ratio(10))))
  79. let get = self.echo.get(.with { $0.text = "foo" })
  80. let initialMetadata = self.expectation(description: "received initial metadata")
  81. get.initialMetadata.map {
  82. $0.first(name: "grpc-encoding")
  83. }.assertEqual("deflate", fulfill: initialMetadata)
  84. let status = self.expectation(description: "received status")
  85. get.status.map {
  86. $0.code
  87. }.assertEqual(.ok, fulfill: status)
  88. self.wait(for: [initialMetadata, status], timeout: self.defaultTimeout)
  89. }
  90. func testServerCanDecompressNonAdvertisedButSupportedCompression() throws {
  91. // Server should be able to decompress a format it supports but does not advertise. In doing
  92. // so it must also return a "grpc-accept-encoding" header which includes the value it did not
  93. // advertise.
  94. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [.gzip], decompressionLimit: .ratio(10))))
  95. self.setupClient(encoding: .enabled(.init(forRequests: .deflate, acceptableForResponses: [], decompressionLimit: .ratio(10))))
  96. let get = self.echo.get(.with { $0.text = "foo" })
  97. let initialMetadata = self.expectation(description: "received initial metadata")
  98. get.initialMetadata.map {
  99. $0[canonicalForm: "grpc-accept-encoding"]
  100. }.assertEqual(["gzip", "deflate"], fulfill: initialMetadata)
  101. let status = self.expectation(description: "received status")
  102. get.status.map {
  103. $0.code
  104. }.assertEqual(.ok, fulfill: status)
  105. self.wait(for: [initialMetadata, status], timeout: self.defaultTimeout)
  106. }
  107. func testServerCompressesResponseWithDifferentAlgorithmToRequest() throws {
  108. // Server should be able to compress responses with a different method to the client, providing
  109. // the client supports it.
  110. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [.gzip], decompressionLimit: .ratio(10))))
  111. self.setupClient(encoding: .enabled(.init(forRequests: .deflate, acceptableForResponses: [.deflate, .gzip], decompressionLimit: .ratio(10))))
  112. let get = self.echo.get(.with { $0.text = "foo" })
  113. let initialMetadata = self.expectation(description: "received initial metadata")
  114. get.initialMetadata.map {
  115. $0.first(name: "grpc-encoding")
  116. }.assertEqual("gzip", fulfill: initialMetadata)
  117. let status = self.expectation(description: "received status")
  118. get.status.map {
  119. $0.code
  120. }.assertEqual(.ok, fulfill: status)
  121. self.wait(for: [initialMetadata, status], timeout: self.defaultTimeout)
  122. }
  123. func testCompressedRequestWithCompressionNotSupportedOnServer() throws {
  124. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [.gzip, .deflate], decompressionLimit: .ratio(10))))
  125. // We can't specify a compression we don't support, so we'll specify no compression and then
  126. // send a 'grpc-encoding' with our initial metadata.
  127. self.setupClient(encoding: .enabled(.init(forRequests: .none, acceptableForResponses: [.deflate, .gzip], decompressionLimit: .ratio(10))))
  128. let headers: HPACKHeaders = ["grpc-encoding": "you-don't-support-this"]
  129. let get = self.echo.get(.with { $0.text = "foo" }, callOptions: CallOptions(customMetadata: headers))
  130. let response = self.expectation(description: "received response")
  131. get.response.assertError(fulfill: response)
  132. let trailers = self.expectation(description: "received trailing metadata")
  133. get.trailingMetadata.map {
  134. $0[canonicalForm: "grpc-accept-encoding"]
  135. }.assertEqual(["gzip", "deflate"], fulfill: trailers)
  136. let status = self.expectation(description: "received status")
  137. get.status.map {
  138. $0.code
  139. }.assertEqual(.unimplemented, fulfill: status)
  140. self.wait(for: [response, trailers, status], timeout: self.defaultTimeout)
  141. }
  142. func testDecompressionLimitIsRespectedByServerForUnaryCall() throws {
  143. try self.setupServer(encoding: .enabled(.init(decompressionLimit: .absolute(1))))
  144. self.setupClient(encoding: .enabled(.init(forRequests: .gzip, decompressionLimit: .absolute(1024))))
  145. let get = self.echo.get(.with { $0.text = "foo" })
  146. let status = self.expectation(description: "received status")
  147. get.status.map {
  148. $0.code
  149. }.assertEqual(.resourceExhausted, fulfill: status)
  150. self.wait(for: [status], timeout: self.defaultTimeout)
  151. }
  152. func testDecompressionLimitIsRespectedByServerForStreamingCall() throws {
  153. try self.setupServer(encoding: .enabled(.init(decompressionLimit: .absolute(1024))))
  154. self.setupClient(encoding: .enabled(.init(forRequests: .gzip, decompressionLimit: .absolute(2048))))
  155. let collect = self.echo.collect()
  156. let status = self.expectation(description: "received status")
  157. // Smaller than limit.
  158. collect.sendMessage(.with { $0.text = "foo" }, promise: nil)
  159. // Should be just over the limit.
  160. collect.sendMessage(.with { $0.text = String(repeating: "x", count: 1024)}, promise: nil)
  161. collect.sendEnd(promise: nil)
  162. collect.status.map {
  163. $0.code
  164. }.assertEqual(.resourceExhausted, fulfill: status)
  165. self.wait(for: [status], timeout: self.defaultTimeout)
  166. }
  167. func testDecompressionLimitIsRespectedByClientForUnaryCall() throws {
  168. try self.setupServer(encoding: .enabled(.init(enabledAlgorithms: [.gzip], decompressionLimit: .absolute(1024))))
  169. self.setupClient(encoding: .enabled(.responsesOnly(decompressionLimit: .absolute(1))))
  170. let get = self.echo.get(.with { $0.text = "foo" })
  171. let status = self.expectation(description: "received status")
  172. get.status.map {
  173. $0.code
  174. }.assertEqual(.resourceExhausted, fulfill: status)
  175. self.wait(for: [status], timeout: self.defaultTimeout)
  176. }
  177. func testDecompressionLimitIsRespectedByClientForStreamingCall() throws {
  178. try self.setupServer(encoding: .enabled(.init(decompressionLimit: .absolute(2048))))
  179. self.setupClient(encoding: .enabled(.init(forRequests: .gzip, decompressionLimit: .absolute(1024))))
  180. var responses: [Echo_EchoResponse] = []
  181. let update = self.echo.update {
  182. responses.append($0)
  183. }
  184. let status = self.expectation(description: "received status")
  185. // Smaller than limit.
  186. update.sendMessage(.with { $0.text = "foo" }, promise: nil)
  187. // Should be just over the limit.
  188. update.sendMessage(.with { $0.text = String(repeating: "x", count: 1024)}, promise: nil)
  189. update.sendEnd(promise: nil)
  190. update.status.map {
  191. $0.code
  192. }.assertEqual(.resourceExhausted, fulfill: status)
  193. self.wait(for: [status], timeout: self.defaultTimeout)
  194. XCTAssertEqual(responses.count, 1)
  195. }
  196. }