HTHost: Should close connection when killing pipeline [patch]

Hello,

here is the patch of the day...

I believe that HTHost_killPipe() does not behave correctly - it (or
rather the private function killPipeline() called by it) should close
the HTTP/1.1 pipelined connection. Most of the time, the fact that it
doesn't close it is not a problem, because when the event object of
the connection next receives some data, it notices that nobody is
listening to it, and its correct reaction is to close the connection:

  Host Event.. host 0x82998b0 `localhost' closed connection.
  Host Event.. Host 0x82998b0 `localhost' had 255 extraneous bytes: `...'
  Host Event.. Host 0x82998b0 `localhost' had 255 extraneous bytes: `...'
  ...
  Host Event.. Host 0x82998b0 `localhost' had 255 extraneous bytes: `...'
  Host Event.. Host 0x82998b0 `localhost' had 255 extraneous bytes: `...'
  Host Event.. Host 0x82998b0 `localhost' had 202 extraneous bytes: `...'
  Channel..... Delete 0x8284290 with semaphore 0, status 0
  Channel..... Delete input stream 0x82b7e40 from channel 0x8284290
  Channel..... Delete input stream 0x82b7e40 from channel 0x8284290
  Socket read. FREEING....
  Socket write FREEING....
  Net Manager. Decreasing active sockets to 0, 1 persistent sockets
  Channel..... Deleted 0x8284290, socket 9
  Net Manager. 0 active sockets, decreasing persistent sockets to 0
  Host info... removed host 0x82998b0 as persistent
  Timer....... Dispatch timer 0x81f8870
  Timer....... Deleted expired timer 0x81f8870
  Host Event.. host 0x82998b0 `localhost' closed connection.

However, things break in the following case:

  - We call HTHost_killPipe() while the pipeline is active, in the
    middle of a download
  - The requests of the pipeline are removed from the queue, but the
    connection is still open
  - Instead of executing the main loop, we *immediately* start another
    request for the same host.
  - Because the connection is still up, the request is added to the
    pipeline and the HTTP request headers are sent off
  - The new request's event object now "listens" to the connection
  - Instead of the HTTP reply headers it expects, it only gets what it
    thinks is "garbage" - in fact, it is the remaining data of the
    request which was aborted by HTHost_killPipe() earlier
  - libwww concludes that "this is a HTTP 0.9 or earlier host" and
    aborts the new request with an error :-(

The libwww log output for this case is appended at the end of this
mail.

If you don't want to hack libwww, a fix for this problem is always to
make the following calls yourself after calling HTHost_killPipe():

  HTChannel_setSemaphore(host->channel, 0);
  HTHost_clearChannel(host, HT_INTERRUPTED);

However, the nicer fix IMHO is the patch below, which closes the
connection inside killPipeline(), but only if the pipeline isn't idle,
i.e. it contains 1 or more requests.

Cheers,
  
  Richard

-- 
  __   _
  |_) /|  Richard Atterer     |  CS student at the Technische  |  GnuPG key:
  | \/¯|  http://atterer.net  |  Universität München, Germany  |  0x888354F7
  ¯ '` ¯

--- libwww-HTHost.c.orig	2003-02-17 23:43:12.000000000 +0100
+++ HTHost.c	2003-03-03 19:03:01.000000000 +0100
@@ -140,6 +140,10 @@
 		    (*net->event.cbf)(HTChannel_socket(host->channel), net->event.param, type);
 		}
 	    }
+	    /* Close the connection, to prevent the server from sending us
+	       the remainder of the data for the currently active request. */
+	    HTChannel_setSemaphore(host->channel, 0);
+	    HTHost_clearChannel(host, HT_INTERRUPTED);
 	}
 	return YES;
     }

----------------------------------------------------------------------
The bug in action:

Because the user clicked on "reload", my application calls
HTHost_killPipe() to abort the current request.

  Host kill... Pipeline due to HTEvent_CLOSE event
  Host kill... Terminating net object 0x8259ed0 from pipe line
  Error....... Add  59	Severity: 1	Parameter: `Unspecified'	Where: `HTLoadHTTP'
  HTTP Clean.. Called with status -902, net 0x8259ed0
  Alert 64 for http://localhost/test obj 0x829abf4
  HTTPGen..... ABORTING...
  HTTPRequest. ABORTING...
  Buffer...... ABORTING...
  FileWriter.. ABORTING...
  Net Object.. Delete 0x8259ed0 and call AFTER filters
  Host info... Remove 0x8259ed0 from pipe

NB: The socket is kept open because it's a persistent HTTP/1.1 connection.

  Host Object. keeping persistent socket 9
  Channel..... Delete 0x81f8918 with semaphore 1, status -902
  Channel..... Delete input stream 0x82a8818 from channel 0x81f8918
  MIME........ ABORTING...
  HTTPStatus.. ABORTING...
  FileWriter.. ABORTING...
  Channel..... Delete input stream 0x82a8818 from channel 0x81f8918
  Buffer...... ABORTING...
  Channel..... Semaphore decreased to 0 for channel 0x81f8918
  Timer....... Created one shot timer 0x81f8818 with callback 0x806d164, context 0x82a67a0, and relative timeout 60000
  Host........ Object 0x82a67a0 going idle...
  Net Object.. Check for pending Net objects
  Net Object.. Freeing object 0x8259ed0
  Net After... calling 0x8068e46 (request 0x8259bf8, response 0x827ceb0, status -902, context (nil))
  Net After... calling 0x80679b2 (request 0x8259bf8, response 0x827ceb0, status -902, context (nil))
  Request..... Delete 0x8259bf8
  Request..... Deleting dangling output stream
  Response.... Delete 0x827ceb0

HTNet_killPipe() returns, and immediately I start a new request for
the same host:

  Request..... Created 0x82b4ec8
  HTSimplify.. `http://localhost/test' into
  ............ `http://localhost/test'
  Net Before.. calling 0x8068dc4 (request 0x82b4ec8, context (nil))
  Auth Engine. Looking up `http://localhost/test'
  URL Tree.... did NOT find `w3c-AA'
  Auth Engine. No information
  Net Before.. calling 0x8068cca (request 0x82b4ec8, context (nil))
  Net Object.. 0x8259ed0 created with hash 1
  Net Object.. starting request 0x82b4ec8 (retry=1) with net object 0x8259ed0
  HTTP........ Looking for `http://localhost/test'
  HTHost parse Looking up `localhost' on port 8000

Waah, it's going to reuse the connection:

  Host info... REUSING CHANNEL 0x81f8918
  Host info... Added Net 0x8259ed0 (request 0x82b4ec8) to pipe on Host 0x82a67a0, 2 requests made, 1 requests in pipe, 0 pending
  Timer....... Deleted active timer 0x81f8818
  HTHost...... No ActivateRequest callback handler registered
  Channel..... Semaphore increased to 1 for channel 0x81f8918
  Host connect Unlocking Host 0x82a67a0
  StreamStack. Constructing stream stack for text/x-http to */*
  Tee......... Created stream 0x81f8818 with resolver 0x404f0e90
  HTTP........ Dumping response to `w3chttp.out'
  Tee......... Created stream 0x825a100 with resolver 0x404f0e90
  HTTP........ Dumping request to `w3chttp.out'
  HTTP........ Generating HTTP/1.x Request Headers
  HTTP........ Generating General Headers
  Buffer...... Flushing 0x829ad00
  Write Socket 196 bytes written to 9
  Alert 16 for http://localhost/test obj 0x829abf4
  Read Socket. WOULD BLOCK fd 9
  glibwww_io_func: event 0x82a6860, event->cbf 0x806d19e
  Host Event.. READ passed to `http://localhost/test'
  Read Socket. 25601 bytes read from socket 9
  Response.... Created 0x827d828

Garbage is received:

  Error....... Add  62	Severity: 8	Parameter: `993àBu$Þ±)oUuçüå@sRIWAË<2º]êFm©ºS)&Ò¤/AyØäö°PRÀìÛjµlôph@éÐ_ ,³DÀ¤7åÎÄÚ¼ÎÓÆ+ÁÝ£'	Where: `HTTPStatusStream'
  StreamStack. Constructing stream stack for www/unknown to */*
  Host........ passing 100 bytes as consumed to 0x82a8818
  Host........ 25501 bytes remaining 
  HTTP Status. `localhost' is probably a broken 1.0 server that doesn't understand HEAD
  Read Socket. Target ERROR -1
  Host kill... Pipeline due to HTEvent_CLOSE event
  ...

An error is returned to my app; "This is probably a HTTP server 0.9 or
less".

Received on Monday, 3 March 2003 14:23:07 UTC