PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/finagle-stream/src/test/scala/com/twitter/finagle/stream/EndToEndSpec.scala

https://gitlab.com/github-cloud-corp/finagle
Scala | 324 lines | 269 code | 47 blank | 8 comment | 7 complexity | d3af9408d09645a7f6c21355c1996f9b MD5 | raw file
  1. package com.twitter.finagle.stream
  2. import com.twitter.concurrent._
  3. import com.twitter.conversions.time._
  4. import com.twitter.finagle.builder.{ClientBuilder, ServerBuilder}
  5. import com.twitter.finagle.{Service, ServiceProxy, TooManyConcurrentRequestsException}
  6. import com.twitter.util._
  7. import java.net.{InetSocketAddress, SocketAddress}
  8. import java.nio.charset.Charset
  9. import java.util.concurrent.Executors
  10. import org.jboss.netty.bootstrap.ClientBootstrap
  11. import org.jboss.netty.buffer.{ChannelBuffer, ChannelBuffers}
  12. import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory
  13. import org.jboss.netty.channel.{ChannelEvent, ChannelHandlerContext, ChannelPipelineFactory, ChannelState, ChannelStateEvent, ChannelUpstreamHandler, Channels, MessageEvent, WriteCompletionEvent}
  14. import org.jboss.netty.handler.codec.http._
  15. import org.specs.SpecificationWithJUnit
  16. class EndToEndSpec extends SpecificationWithJUnit {
  17. case class MyStreamResponse(
  18. httpResponse: HttpResponse,
  19. messages: Offer[ChannelBuffer],
  20. error: Offer[Throwable])
  21. extends StreamResponse
  22. {
  23. val released = new Promise[Unit]
  24. def release() = released.updateIfEmpty(Return(()))
  25. }
  26. class MyService(response: StreamResponse)
  27. extends Service[HttpRequest, StreamResponse]
  28. {
  29. def apply(request: HttpRequest) = Future.value(response)
  30. }
  31. "Streams" should {
  32. val httpRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/")
  33. val httpResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
  34. val messages = new Broker[ChannelBuffer]
  35. val error = new Broker[Throwable]
  36. val serverRes = MyStreamResponse(httpResponse, messages.recv, error.recv)
  37. def workIt(what: String)(mkClient: => (Service[HttpRequest, StreamResponse], SocketAddress)) {
  38. what in {
  39. val (client, address) = mkClient
  40. doAfter { client.close() }
  41. "writes from the server arrive on the client's channel" in {
  42. val clientRes = Await.result(client(httpRequest), 1.second)
  43. var result = ""
  44. val latch = new CountDownLatch(1)
  45. (clientRes.error?) ensure {
  46. Future { latch.countDown() }
  47. }
  48. clientRes.messages foreach { channelBuffer =>
  49. Future {
  50. result += channelBuffer.toString(Charset.defaultCharset)
  51. }
  52. }
  53. messages !! ChannelBuffers.wrappedBuffer("1".getBytes)
  54. messages !! ChannelBuffers.wrappedBuffer("2".getBytes)
  55. messages !! ChannelBuffers.wrappedBuffer("3".getBytes)
  56. error !! EOF
  57. latch.within(1.second)
  58. result mustEqual "123"
  59. }
  60. "writes from the server are queued before the client responds" in {
  61. val clientRes = Await.result(client(httpRequest), 1.second)
  62. messages !! ChannelBuffers.wrappedBuffer("1".getBytes)
  63. messages !! ChannelBuffers.wrappedBuffer("2".getBytes)
  64. messages !! ChannelBuffers.wrappedBuffer("3".getBytes)
  65. val latch = new CountDownLatch(3)
  66. var result = ""
  67. clientRes.messages foreach { channelBuffer =>
  68. Future {
  69. result += channelBuffer.toString(Charset.defaultCharset)
  70. latch.countDown()
  71. }
  72. }
  73. latch.within(1.second)
  74. error !! EOF
  75. result mustEqual "123"
  76. }
  77. "the client does not admit concurrent requests" in {
  78. val clientRes = Await.result(client(httpRequest), 1.second)
  79. client(httpRequest).poll must beLike {
  80. case Some(Throw(_: TooManyConcurrentRequestsException)) => true
  81. }
  82. }
  83. skip("the server does not admit concurrent requests")
  84. "the server does not admit concurrent requests" in {
  85. // The finagle client, by nature, doesn't allow for this, so
  86. // we need to go through the trouble of establishing our own
  87. // pipeline.
  88. val recvd = new Broker[ChannelEvent]
  89. val bootstrap = new ClientBootstrap(
  90. new NioClientSocketChannelFactory(
  91. Executors.newCachedThreadPool(),
  92. Executors.newCachedThreadPool()))
  93. bootstrap.setPipelineFactory(new ChannelPipelineFactory {
  94. override def getPipeline() = {
  95. val pipeline = Channels.pipeline()
  96. pipeline.addLast("httpCodec", new HttpClientCodec)
  97. pipeline.addLast("recvd", new ChannelUpstreamHandler {
  98. override def handleUpstream(ctx: ChannelHandlerContext, e: ChannelEvent) {
  99. val keep = e match {
  100. case se: ChannelStateEvent =>
  101. se.getState == ChannelState.OPEN
  102. case _: WriteCompletionEvent => false
  103. case _ => true
  104. }
  105. if (keep) recvd ! e
  106. }
  107. })
  108. pipeline
  109. }
  110. })
  111. doAfter {
  112. bootstrap.releaseExternalResources()
  113. }
  114. val connectFuture = bootstrap
  115. .connect(address)
  116. .awaitUninterruptibly()
  117. connectFuture.isSuccess must beTrue
  118. val channel = connectFuture.getChannel
  119. doAfter {
  120. channel.close()
  121. }
  122. // first request is accepted
  123. channel
  124. .write(httpRequest)
  125. .awaitUninterruptibly()
  126. .isSuccess must beTrue
  127. messages !! ChannelBuffers.wrappedBuffer("chunk1".getBytes)
  128. Await.result(recvd?, 1.second) must beLike {
  129. case e: ChannelStateEvent =>
  130. e.getState == ChannelState.OPEN && (java.lang.Boolean.TRUE equals e.getValue)
  131. }
  132. Await.result(recvd?, 1.second) must beLike {
  133. case m: MessageEvent =>
  134. m.getMessage must beLike {
  135. case res: HttpResponse => res.isChunked
  136. }
  137. }
  138. Await.result(recvd?, 1.second) must beLike {
  139. case m: MessageEvent =>
  140. m.getMessage must beLike {
  141. case res: HttpChunk => !res.isLast // get "chunk1"
  142. }
  143. }
  144. // The following requests should be ignored
  145. channel
  146. .write(httpRequest)
  147. .awaitUninterruptibly()
  148. .isSuccess must beTrue
  149. // the streaming should continue
  150. messages !! ChannelBuffers.wrappedBuffer("chunk2".getBytes)
  151. Await.result(recvd?, 1.second) must beLike {
  152. case m: MessageEvent =>
  153. m.getMessage must beLike {
  154. case res: HttpChunk => !res.isLast // get "chunk2"
  155. }
  156. }
  157. error !! EOF
  158. Await.result(recvd?, 1.second) must beLike {
  159. case m: MessageEvent =>
  160. m.getMessage must beLike {
  161. case res: HttpChunkTrailer => res.isLast
  162. }
  163. }
  164. // And finally it's closed.
  165. Await.result(recvd?, 1.second) must beLike {
  166. case e: ChannelStateEvent =>
  167. e.getState == ChannelState.OPEN && (java.lang.Boolean.FALSE equals e.getValue)
  168. }
  169. }
  170. "server ignores channel buffer messages after channel close" in {
  171. val clientRes = Await.result(client(httpRequest), 1.second)
  172. var result = ""
  173. val latch = new CountDownLatch(1)
  174. (clientRes.error?) ensure {
  175. Future { latch.countDown() }
  176. }
  177. clientRes.messages foreach { channelBuffer =>
  178. Future {
  179. result += channelBuffer.toString(Charset.defaultCharset)
  180. }
  181. }
  182. FuturePool.unboundedPool {
  183. messages !! ChannelBuffers.wrappedBuffer("12".getBytes)
  184. messages !! ChannelBuffers.wrappedBuffer("23".getBytes)
  185. error !! EOF
  186. messages !! ChannelBuffers.wrappedBuffer("34".getBytes)
  187. }
  188. latch.within(1.second)
  189. result mustEqual "1223"
  190. }
  191. }
  192. }
  193. workIt("straight") {
  194. val server = ServerBuilder()
  195. .codec(new Stream)
  196. .bindTo(new InetSocketAddress(0))
  197. .name("Streams")
  198. .build(new MyService(serverRes))
  199. val address = server.localAddress
  200. val factory = ClientBuilder()
  201. .codec(new Stream)
  202. .hosts(Seq(address))
  203. .hostConnectionLimit(1)
  204. .buildFactory()
  205. val underlying = Await.result(factory())
  206. val client = new ServiceProxy[HttpRequest, StreamResponse](underlying) {
  207. override def close(deadline: Time) =
  208. Closable.all(underlying, server, factory).close(deadline)
  209. }
  210. (client, address)
  211. }
  212. workIt("proxy") {
  213. val server = ServerBuilder()
  214. .codec(new Stream)
  215. .bindTo(new InetSocketAddress(0))
  216. .name("streamserver")
  217. .build(new MyService(serverRes))
  218. val serverClient = ClientBuilder()
  219. .codec(new Stream)
  220. .hosts(Seq(server.localAddress))
  221. .hostConnectionLimit(1)
  222. .build()
  223. val proxy = ServerBuilder()
  224. .codec(new Stream)
  225. .bindTo(new InetSocketAddress(0))
  226. .name("streamproxy")
  227. .build(serverClient)
  228. val factory = ClientBuilder()
  229. .codec(new Stream)
  230. .hosts(Seq(proxy.localAddress))
  231. .hostConnectionLimit(1)
  232. .buildFactory()
  233. val underlying = Await.result(factory())
  234. val client = new ServiceProxy[HttpRequest, StreamResponse](underlying) {
  235. override def close(deadline: Time) =
  236. Closable.all(server, serverClient, proxy, factory).close(deadline)
  237. }
  238. (client, proxy.localAddress)
  239. }
  240. // TODO: investigate test flakiness (CSL-117)
  241. "delay release until complete response" in {
  242. if (false) {
  243. @volatile var count: Int = 0
  244. val server = ServerBuilder()
  245. .codec(new Stream)
  246. .bindTo(new InetSocketAddress(0))
  247. .name("Streams")
  248. .build((new MyService(serverRes)) map { r: HttpRequest =>
  249. synchronized { count += 1 }
  250. r
  251. })
  252. val client = ClientBuilder()
  253. .codec(new Stream)
  254. .hosts(Seq(server.localAddress))
  255. .hostConnectionLimit(1)
  256. .retries(2)
  257. .build()
  258. doAfter {
  259. client.close()
  260. server.close()
  261. }
  262. val res = Await.result(client(httpRequest), 1.second)
  263. count must be_==(1)
  264. val f2 = client(httpRequest)
  265. f2.poll must beNone // because of the host connection limit
  266. messages !! ChannelBuffers.wrappedBuffer("1".getBytes)
  267. (res.messages??).toString(Charset.defaultCharset) must be_==("1")
  268. count must be_==(1)
  269. error !! EOF
  270. res.release()
  271. val res2 = Await.result(f2, 1.second)
  272. count must be_==(2)
  273. res2.release()
  274. }
  275. }
  276. }
  277. }