[ 
https://issues.apache.org/jira/browse/HTTPCLIENT-2301?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17770097#comment-17770097
 ] 

Pierre N. edited comment on HTTPCLIENT-2301 at 9/28/23 3:00 PM:
----------------------------------------------------------------

I expected this kind of response because that's exactly what I thought when I 
encountered this bug...

For the story, I discovered this bug in production with 
[Serposcope|http://atlassian.com/]. One of my user had [inverted 
response|https://forum.serposcope.com/d/101-error-with-scrapingant-api], when 
sending a request to Google it would sometimes have the response of a previous 
request.

It was hell to debug... To the point I suspected, like you, the server was 
responsible for this. I would never have expected a library I have been using 
for a decade to have such bug.

But in my production code I don't use Wiremock... I just used it to 
troubleshooting and reproduce this bug.

But here you go, the same bug using httpbin.org, no mocking :


{color:#0033b3}internal class 
{color}{color:#000000}HttpClientRaceConditionDebug {color}{

  {color:#9e880d}@Test
{color}{color:#9e880d}  {color}{color:#0033b3}fun 
{color}{color:#00627a}bugbug{color}() { 
   {color:#0033b3}val {color}{color:#000000}client {color}= 
{color:#000000}HttpClients{color}.custom()
    .setConnectionManager(BasicHttpClientConnectionManager())
{color:#8c8c8c}// .setConnectionManager(PoolingHttpClientConnectionManager())
{color}   .setDefaultRequestConfig(
    {color:#000000}RequestConfig{color}.custom()
     
.setResponseTimeout({color:#000000}Timeout{color}.ofSeconds({color:#1750eb}1{color}))
     .build()
    )
   .build()

   
{color:#000000}client{color}.{color:#00627a}executeAndLog{color}({color:#067d17}"https://httpbin.org/anything?1"{color})
   
{color:#000000}client{color}.{color:#00627a}executeAndLog{color}({color:#067d17}"https://httpbin.org/delay/2?2"{color})
   
{color:#000000}client{color}.{color:#00627a}executeAndLog{color}({color:#067d17}"https://httpbin.org/delay/2?2"{color})
   
{color:#000000}client{color}.{color:#00627a}executeAndLog{color}({color:#067d17}"https://httpbin.org/anything?3"{color})
   
{color:#000000}client{color}.{color:#00627a}executeAndLog{color}({color:#067d17}"https://httpbin.org/anything?3"{color})
   
{color:#000000}client{color}.{color:#00627a}executeAndLog{color}({color:#067d17}"https://httpbin.org/anything?3"{color})
}

{color:#0033b3}private fun 
{color}{color:#000000}CloseableHttpClient{color}.{color:#00627a}executeAndLog{color}(uri:
 {color:#000000}String{color}) {
  {color:#0033b3}try {color}{
   execute(HttpGet({color:#000000}uri{color})) { 
{color:#00627a}println{color}({color:#067d17}"{color}{color:#0037a6}${color}{color:#000000}uri{color}{color:#067d17}
 -> 
{color}{color:#0037a6}${{color}{color:#000000}EntityUtils{color}.toString(it.{color:#871094}entity{color}).{color:#00627a}split{color}({color:#067d17}"{color}{color:#0037a6}\n{color}{color:#067d17}"{color}).{color:#00627a}filter
 {color}{ it.{color:#00627a}contains{color}({color:#067d17}"url"{color}) 
}{color:#0037a6}}{color}{color:#067d17}"{color}) }
  } {color:#0033b3}catch {color}({color:#000000}ex{color}: 
{color:#000000}Exception{color}) {
  
{color:#00627a}println{color}({color:#067d17}"{color}{color:#0037a6}${color}{color:#000000}uri{color}{color:#067d17}
 -> 
{color}{color:#0037a6}${color}{color:#000000}ex{color}{color:#067d17}"{color})
  }
}
}
It returns:
{quote}[https://httpbin.org/anything?1] -> [ "url": 
"https://httpbin.org/anything?1";]
[https://httpbin.org/delay/2?2] -> java.net.SocketTimeoutException: Read timed 
out
[https://httpbin.org/delay/2?2] -> java.net.SocketTimeoutException: Read timed 
out
[https://httpbin.org/anything?3] -> java.net.SocketTimeoutException: Read timed 
out
[https://httpbin.org/anything?3] -> [ "url": "https://httpbin.org/delay/2?2";]
[https://httpbin.org/anything?3] -> [ "url": "https://httpbin.org/anything?3";]
{quote}
Replace with PoolingHttpClientConnectionManager and it will work correctly :
{quote}[https://httpbin.org/anything?1] -> [ "url": 
"https://httpbin.org/anything?1";]
[https://httpbin.org/delay/2?2] -> java.net.SocketTimeoutException: Read timed 
out
[https://httpbin.org/delay/2?2] -> java.net.SocketTimeoutException: Read timed 
out
[https://httpbin.org/anything?3] -> [ "url": "https://httpbin.org/anything?3";]
[https://httpbin.org/anything?3] -> [ "url": "https://httpbin.org/anything?3";]
[https://httpbin.org/anything?3] -> [ "url": "https://httpbin.org/anything?3";]
{quote}
Do you think the bug is also on httpbin.org ? :P


was (Author: pierz):
I expected this kind of response because that's exactly what I thought when I 
encountered this bug...

For the story, I discovered this bug in production with 
[Serposcope|http://atlassian.com]. One of my user had [inverted 
response|https://forum.serposcope.com/d/101-error-with-scrapingant-api], when 
sending a request to Google it would sometimes have the response of a previous 
request.

It was hell to debug... To the point I suspected, like you, the server was 
responsible for this. I would never have expected a library I have been using 
for a decade to have such bug. 

But in my production code I don't use Wiremock... I just used it to 
troubleshooting and reproduce this bug.

But here you go, the same bug using httpbin.org, no mocking : 

{quote}
internal class HttpClientDebug {

    @Test
    fun bugbug() {
        val client = HttpClients.custom()
            .setConnectionManager(BasicHttpClientConnectionManager())
//            .setConnectionManager(PoolingHttpClientConnectionManager())
            .setDefaultRequestConfig(
                RequestConfig.custom()
                    .setResponseTimeout(Timeout.ofSeconds(1))
                    .build()
            )
            .build()

        client.executeAndLog("https://httpbin.org/anything?1";)
        client.executeAndLog("https://httpbin.org/delay/2?2";)
        client.executeAndLog("https://httpbin.org/delay/2?2";)
        client.executeAndLog("https://httpbin.org/anything?3";)
        client.executeAndLog("https://httpbin.org/anything?3";)
        client.executeAndLog("https://httpbin.org/anything?3";)
    }

    private fun CloseableHttpClient.executeAndLog(uri: String) {
        try {
            execute(HttpGet(uri)) { println("$uri -> 
${EntityUtils.toString(it.entity).split("\n").filter { it.contains("url") }}") }
        } catch (ex: Exception) {
            println("$uri -> $ex")
        }
    }
}
{quote}

It returns: 

{quote}
https://httpbin.org/anything?1 -> [  "url": "https://httpbin.org/anything?1";]
https://httpbin.org/delay/2?2 -> java.net.SocketTimeoutException: Read timed out
https://httpbin.org/delay/2?2 -> java.net.SocketTimeoutException: Read timed out
https://httpbin.org/anything?3 -> java.net.SocketTimeoutException: Read timed 
out
https://httpbin.org/anything?3 -> [  "url": "https://httpbin.org/delay/2?2";]
https://httpbin.org/anything?3 -> [  "url": "https://httpbin.org/anything?3";]
{quote}

Replace with PoolingHttpClientConnectionManager and it will work correctly : 

{quote}
https://httpbin.org/anything?1 -> [  "url": "https://httpbin.org/anything?1";]
https://httpbin.org/delay/2?2 -> java.net.SocketTimeoutException: Read timed out
https://httpbin.org/delay/2?2 -> java.net.SocketTimeoutException: Read timed out
https://httpbin.org/anything?3 -> [  "url": "https://httpbin.org/anything?3";]
https://httpbin.org/anything?3 -> [  "url": "https://httpbin.org/anything?3";]
https://httpbin.org/anything?3 -> [  "url": "https://httpbin.org/anything?3";]
{quote} 

Do you think the bug is also on httpbin.org ? :p

> BasicHttpClientConnectionManager returns response of previous requests
> ----------------------------------------------------------------------
>
>                 Key: HTTPCLIENT-2301
>                 URL: https://issues.apache.org/jira/browse/HTTPCLIENT-2301
>             Project: HttpComponents HttpClient
>          Issue Type: Bug
>          Components: HttpClient (classic)
>    Affects Versions: 5.2.1, 5.3-alpha1
>         Environment: JAVA 18
> kotlin 1.7.22
> Ubuntu 22.04 x64
>            Reporter: Pierre N.
>            Priority: Major
>         Attachments: test.kt
>
>
> BasicHttpClientConnectionManager doesn't handle request timeout correctly and 
> return the response of a previous request.
> The unit test below should output:
> {quote}
> http://localhost:41751/1 -> 1
> http://localhost:41751/2 -> java.net.SocketTimeoutException: Read timed out
> http://localhost:41751/2 -> java.net.SocketTimeoutException: Read timed out
> http://localhost:41751/3 -> 3
> http://localhost:41751/3 -> 3
> http://localhost:41751/3 -> 3
> {quote}
> But instead it returns:
> {quote}
> http://localhost:33875/1 -> 1
> http://localhost:33875/2 -> java.net.SocketTimeoutException: Read timed out
> http://localhost:33875/2 -> java.net.SocketTimeoutException: Read timed out
> http://localhost:33875/3 -> java.net.SocketTimeoutException: Read timed out
> http://localhost:33875/3 -> 2
> http://localhost:33875/3 -> 3
> {quote}
> As you can see it returns 2 when requesting the uri /3 which returned 3.
> Also it timeout on the first request to /3 while it shouldn't.
> Replace with PoolingHttpClientConnectionManager() and it works as expected.
> Kotlin unit test to reproduce :
> {quote}
> import com.github.tomakehurst.wiremock.client.WireMock.*
> import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
> import com.github.tomakehurst.wiremock.junit5.WireMockTest
> import org.apache.hc.client5.http.classic.methods.HttpGet
> import org.apache.hc.client5.http.config.RequestConfig
> import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
> import org.apache.hc.client5.http.impl.classic.HttpClients
> import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager
> import org.apache.hc.core5.http.io.entity.EntityUtils
> import org.apache.hc.core5.util.Timeout
> import org.junit.jupiter.api.Test
> @WireMockTest
> internal class HttpClientRaceConditionDebug {
>     @Test
>     fun `debugging z`(wm: WireMockRuntimeInfo) {
>         stubFor(get(urlEqualTo("/1")).willReturn(aResponse().withBody("1")))
>         
> stubFor(get(urlEqualTo("/2")).willReturn(aResponse().withFixedDelay(2000).withBody("2")))
>         stubFor(get(urlEqualTo("/3")).willReturn(aResponse().withBody("3")))
>         val client = HttpClients.custom()
>             .setConnectionManager(BasicHttpClientConnectionManager())
> //            .setConnectionManager(PoolingHttpClientConnectionManager())
>             .setDefaultRequestConfig(
>                 RequestConfig.custom()
>                     .setResponseTimeout(Timeout.ofSeconds(1))
>                     .build()
>             )
>             .build()
>         client.executeAndLog("${wm.httpBaseUrl}/1")
>         client.executeAndLog("${wm.httpBaseUrl}/2")
>         client.executeAndLog("${wm.httpBaseUrl}/2")
>         client.executeAndLog("${wm.httpBaseUrl}/3")
>         client.executeAndLog("${wm.httpBaseUrl}/3")
>         client.executeAndLog("${wm.httpBaseUrl}/3")
>     }
>     private fun CloseableHttpClient.executeAndLog(uri: String) {
>         try {
>             execute(HttpGet(uri)) { println("$uri -> 
> ${EntityUtils.toString(it.entity)}") }
>         } catch (ex: Exception) {
>             println("$uri -> $ex")
>         }
>     }
> }
> {quote}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@hc.apache.org
For additional commands, e-mail: dev-h...@hc.apache.org

Reply via email to