This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Lib/httplib.py: Resend http request on server close connection
Type: enhancement Stage: resolved
Components: Versions: Python 2.7
process
Status: closed Resolution: third party
Dependencies: Superseder:
Assigned To: Nosy List: gmixo, martin.panter, r.david.murray
Priority: normal Keywords: patch

Created on 2015-12-11 07:36 by gmixo, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
httplib.patch gmixo, 2015-12-11 07:40 review
httplib.pcapng gmixo, 2015-12-11 10:49 pcap trace for get and post over httplib
Выделение_058.png gmixo, 2015-12-11 10:50 simple trace for put request with establishing connection
Выделение_059.png gmixo, 2015-12-11 10:51 trace for put request in same connection and establish connection on failure, this trace for single request
Выделение_061.png gmixo, 2015-12-11 10:54 httplib.pcapng but in image
Messages (9)
msg256204 - (view) Author: Mikhail Gulyaev (gmixo) * Date: 2015-12-11 07:36
Hello guys!

Recently I recived some strange behavior for sending http requests using httplib

My python script uses httplib and interacts with a web server which have a keep-alive timeout 5 seconds. Script makes PUT requests and sends files to server. At first time it works ok. Then after 5 seconds server closes connection. And then I doing second PUT request, that fails in two stages:
 - HTTPConnection trys to send PUT request to closed socket.
 - HTTPConnection reconnects to server and sends request again but no file sended.
This behavior was checked using wireshark and debug output of httplib.

The best solution IMHO would be to checking socket state on each request, and reconnects if needed - but it seems this issue has no unique solution.

So I offer a patch which simply rewinds file before sending if needed. 
hasattr(data,'tell') could be used in checking condition
-----------------------------------
diff -r 002d8b981128 Lib/httplib.py
--- a/Lib/httplib.py	Wed Dec 09 19:44:30 2015 +0200
+++ b/Lib/httplib.py	Fri Dec 11 12:59:47 2015 +0600
@@ -865,6 +865,7 @@
         blocksize = 8192
         if hasattr(data,'read') and not isinstance(data, array):
             if self.debuglevel > 0: print "sendIng a read()able"
+            if data.tell() > 0: data.seek(0) # rewind for retry send file
             datablock = data.read(blocksize)
             while datablock:
                 self.sock.sendall(datablock)
-----------------------------------
msg256212 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2015-12-11 10:26
Sorry but I have a few concerns about your patch:

Why does this have to be in the HTTPConnection.send() method? Why can’t you do the file rewinding yourself, when you retry the request?

This could break compatibility if someone wrote code that expects data to be sent from a non-zero file position.

I think this would be new feature (for the next version of Python), and couldn’t be accepted as a bug fix for 2.7.

See also Issue 9740 (persistent HTTP client connections), where I suggested a few things you mentioned, including polling for an unsolicited response or closed connection, and reconnecting before sending a request.

Also beware that a general HTTP client shouldn’t automatically retry idempotent requests if there is a chance that the original request was already received. PUT is idempotent so that is okay. POST is not, however.
msg256213 - (view) Author: Mikhail Gulyaev (gmixo) * Date: 2015-12-11 10:49
> Why can’t you do the file rewinding yourself, when you retry the request?
Actually I'm not retry to send a request. This behavior I met for httplib library while I'm send a single request, httplib send it to exist connection and then recreates connection on failure and send it again. Maybe this doing tcp socket
For GET POST and DELETE I meet the same behavior

> if someone wrote code that expects data to be sent from a non-zero file position.
Well in this case I'd prefer to trunkate file before send.
msg256220 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-12-11 15:34
This patch is definitely invalid.  There is no requirement that data be seekable.

Are you saying that there insufficient support in http(lib) for an application to handle this server timeout?  Can you provide a program that demonstrates the problem (even better would be a unit test that simulates the server timeout).  I'm thinking it is likely that it is the responsibility of the layer above http(lib) to handle this, but the problem isn't clear enough to me to be sure (Martin probably has a better understanding of it).
msg256249 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2015-12-12 02:00
As far as I understand, httplib should not be automatically reconnecting and re-sending requests. I still suspect your script may be causing the retry, but you are welcome to prove me wrong.

Can you clarify “send PUT request to closed socket”: is the local OS socket closed (file descriptor is released)? Or is it that the remote end of the connection has been shut down? If it is the remote end, in Python 2 usually you would see a BadStatusLine or some kind of socket.error exception, and nothing would be retried.

I think we really need to know what your script is doing to be able to help. For instance, in the 059 screen shot, what API calls were made to cause data to be initially sent (presumably from local port 40736), and then the reconnection (local port 40757) with more data?
msg256255 - (view) Author: Mikhail Gulyaev (gmixo) * Date: 2015-12-12 04:39
You right I found who resend requests!

My request is goes through httplb2(which use httplib and resends request on failure), but the issue is that if request body contains file and then that file is read out and on retry there is nothing to read since we already read it in httplib. 
So what solution could you suggest for me? Is it some patch for httplib2 or totally my own troubles

This issue is on border of interacting httplib and httplib2
 - httplib sends request and reads out a file
 - httplib2 resends a request but file is already readed out

Will it be honest if we rereads file in httplib2? and could we able assume that readable object(hasattr(data,'read') == True) has also tell and seek methods
msg256263 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-12-12 05:20
The issue is definitely in httplib2, then.  You should open an issue on their bug tracker.
msg256266 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2015-12-12 06:08
Okay that makes a lot more sense! I agree that this should either be fixed in httplib2 or in your own script. The problem parallels Issue 5038, where urlopen() is used (rather than httplib2), and the request is retried after getting an authorization failure (rather than after a disconnection).

One option, if you can use Python 3.2+, might be to use a custom iterable object as the body. Then you get a hook to rewind the file every time it is iterated.
msg256267 - (view) Author: Mikhail Gulyaev (gmixo) * Date: 2015-12-12 06:11
Thanks for attention
History
Date User Action Args
2022-04-11 14:58:24adminsetgithub: 70025
2015-12-12 06:11:32gmixosetmessages: + msg256267
2015-12-12 06:08:40martin.pantersetmessages: + msg256266
2015-12-12 05:20:05r.david.murraysetstatus: open -> closed
resolution: third party
messages: + msg256263

stage: test needed -> resolved
2015-12-12 04:39:02gmixosetmessages: + msg256255
2015-12-12 02:01:00martin.pantersetmessages: + msg256249
stage: test needed
2015-12-11 15:34:07r.david.murraysetnosy: + r.david.murray
messages: + msg256220
2015-12-11 10:54:39gmixosetfiles: + Выделение_061.png
2015-12-11 10:51:39gmixosetfiles: + Выделение_059.png
2015-12-11 10:50:36gmixosetfiles: + Выделение_058.png
2015-12-11 10:49:00gmixosetfiles: + httplib.pcapng

messages: + msg256213
2015-12-11 10:26:09martin.pantersetnosy: + martin.panter
messages: + msg256212
2015-12-11 07:40:29gmixosetfiles: + httplib.patch
keywords: + patch
2015-12-11 07:36:46gmixocreate