JaxRsClientTest.groovy 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. /*
  2. * Copyright The OpenTelemetry Authors
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes
  6. import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes
  7. import io.opentelemetry.instrumentation.test.AgentTestTrait
  8. import io.opentelemetry.instrumentation.test.base.HttpClientTest
  9. import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult
  10. import io.opentelemetry.instrumentation.testing.junit.http.SingleConnection
  11. import io.opentelemetry.semconv.SemanticAttributes
  12. import org.apache.cxf.jaxrs.client.spec.ClientBuilderImpl
  13. import org.glassfish.jersey.client.ClientConfig
  14. import org.glassfish.jersey.client.ClientProperties
  15. import org.glassfish.jersey.client.JerseyClientBuilder
  16. import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder
  17. import spock.lang.Unroll
  18. import javax.ws.rs.ProcessingException
  19. import javax.ws.rs.client.ClientBuilder
  20. import javax.ws.rs.client.Entity
  21. import javax.ws.rs.client.Invocation
  22. import javax.ws.rs.client.InvocationCallback
  23. import javax.ws.rs.core.MediaType
  24. import javax.ws.rs.core.Response
  25. import java.util.concurrent.TimeUnit
  26. import static io.opentelemetry.api.trace.SpanKind.CLIENT
  27. import static io.opentelemetry.api.trace.StatusCode.ERROR
  28. abstract class JaxRsClientTest extends HttpClientTest<Invocation.Builder> implements AgentTestTrait {
  29. boolean testRedirects() {
  30. false
  31. }
  32. @Override
  33. boolean testNonStandardHttpMethod() {
  34. false
  35. }
  36. @Override
  37. Invocation.Builder buildRequest(String method, URI uri, Map<String, String> headers) {
  38. return internalBuildRequest(uri, headers)
  39. }
  40. @Override
  41. int sendRequest(Invocation.Builder request, String method, URI uri, Map<String, String> headers) {
  42. try {
  43. def body = BODY_METHODS.contains(method) ? Entity.text("") : null
  44. def response = request.build(method, body).invoke()
  45. // read response body to avoid broken pipe errors on the server side
  46. response.readEntity(String)
  47. try {
  48. response.close()
  49. } catch (IOException ignore) {
  50. }
  51. return response.status
  52. } catch (ProcessingException exception) {
  53. throw exception.getCause()
  54. }
  55. }
  56. @Override
  57. void sendRequestWithCallback(Invocation.Builder request, String method, URI uri, Map<String, String> headers, HttpClientResult requestResult) {
  58. def body = BODY_METHODS.contains(method) ? Entity.text("") : null
  59. request.async().method(method, (Entity) body, new InvocationCallback<Response>() {
  60. @Override
  61. void completed(Response response) {
  62. // read response body
  63. response.readEntity(String)
  64. requestResult.complete(response.status)
  65. }
  66. @Override
  67. void failed(Throwable throwable) {
  68. if (throwable instanceof ProcessingException) {
  69. throwable = throwable.getCause()
  70. }
  71. requestResult.complete(throwable)
  72. }
  73. })
  74. }
  75. private Invocation.Builder internalBuildRequest(URI uri, Map<String, String> headers) {
  76. def client = builder().build()
  77. def service = client.target(uri)
  78. def requestBuilder = service.request(MediaType.TEXT_PLAIN)
  79. headers.each { requestBuilder.header(it.key, it.value) }
  80. return requestBuilder
  81. }
  82. abstract ClientBuilder builder()
  83. @Unroll
  84. def "should properly convert HTTP status #statusCode to span error status"() {
  85. given:
  86. def method = "GET"
  87. def uri = resolveAddress(path)
  88. when:
  89. def actualStatusCode = doRequest(method, uri)
  90. then:
  91. assert actualStatusCode == statusCode
  92. assertTraces(1) {
  93. trace(0, 2) {
  94. span(0) {
  95. hasNoParent()
  96. name "$method"
  97. kind CLIENT
  98. status ERROR
  99. attributes {
  100. "$SemanticAttributes.NETWORK_PROTOCOL_VERSION" "1.1"
  101. "$SemanticAttributes.SERVER_ADDRESS" uri.host
  102. "$SemanticAttributes.SERVER_PORT" uri.port > 0 ? uri.port : { it == null || it == 443 }
  103. "$NetworkAttributes.NETWORK_PEER_ADDRESS" { it == "127.0.0.1" || it == null }
  104. "$SemanticAttributes.URL_FULL" "${uri}"
  105. "$SemanticAttributes.HTTP_REQUEST_METHOD" method
  106. "$SemanticAttributes.HTTP_RESPONSE_STATUS_CODE" statusCode
  107. "$HttpAttributes.ERROR_TYPE" "$statusCode"
  108. }
  109. }
  110. serverSpan(it, 1, span(0))
  111. }
  112. }
  113. where:
  114. path | statusCode
  115. "/client-error" | 400
  116. "/error" | 500
  117. }
  118. }
  119. class JerseyClientTest extends JaxRsClientTest {
  120. @Override
  121. ClientBuilder builder() {
  122. ClientConfig config = new ClientConfig()
  123. config.property(ClientProperties.CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS)
  124. config.property(ClientProperties.READ_TIMEOUT, READ_TIMEOUT_MS)
  125. return new JerseyClientBuilder().withConfig(config)
  126. }
  127. @Override
  128. SingleConnection createSingleConnection(String host, int port) {
  129. // Jersey JAX-RS client uses HttpURLConnection internally, which does not support pipelining nor
  130. // waiting for a connection in the pool to become available. Therefore a high concurrency test
  131. // would require manually doing requests one after another which is not meaningful for a high
  132. // concurrency test.
  133. return null
  134. }
  135. }
  136. class ResteasyClientTest extends JaxRsClientTest {
  137. @Override
  138. ClientBuilder builder() {
  139. return new ResteasyClientBuilder()
  140. .establishConnectionTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
  141. .socketTimeout(READ_TIMEOUT_MS, TimeUnit.MILLISECONDS)
  142. }
  143. @Override
  144. SingleConnection createSingleConnection(String host, int port) {
  145. return new ResteasySingleConnection(host, port)
  146. }
  147. }
  148. class CxfClientTest extends JaxRsClientTest {
  149. @Override
  150. Throwable clientSpanError(URI uri, Throwable exception) {
  151. switch (uri.toString()) {
  152. case "http://localhost:61/": // unopened port
  153. if (exception.getCause() instanceof ConnectException) {
  154. exception = exception.getCause()
  155. }
  156. break
  157. case "https://192.0.2.1/": // non routable address
  158. if (exception.getCause() != null) {
  159. exception = exception.getCause()
  160. }
  161. }
  162. return exception
  163. }
  164. @Override
  165. boolean testWithClientParent() {
  166. !Boolean.getBoolean("testLatestDeps")
  167. }
  168. @Override
  169. boolean testReadTimeout() {
  170. return false
  171. }
  172. @Override
  173. ClientBuilder builder() {
  174. return new ClientBuilderImpl()
  175. .property("http.connection.timeout", (long) CONNECT_TIMEOUT_MS)
  176. .property("org.apache.cxf.transport.http.forceVersion", "1.1")
  177. }
  178. @Override
  179. SingleConnection createSingleConnection(String host, int port) {
  180. // CXF JAX-RS client uses HttpURLConnection internally, which does not support pipelining nor
  181. // waiting for a connection in the pool to become available. Therefore a high concurrency test
  182. // would require manually doing requests one after another which is not meaningful for a high
  183. // concurrency test.
  184. return null
  185. }
  186. }