summaryrefslogtreecommitdiff
path: root/dummyserver
diff options
context:
space:
mode:
authorKevin Burke <kevin@twilio.com>2014-01-18 21:21:43 -0800
committerAndrey Petrov <andrey.petrov@shazow.net>2014-06-25 15:53:24 -0700
commit36378bc3e63eeca05f5d2f40cac6d3197620520f (patch)
tree184e45b76c79999113cb385028c1c9f235f420e7 /dummyserver
parent7393132bdd0e76c8bb41675e1a166b949f607449 (diff)
downloadurllib3-36378bc3e63eeca05f5d2f40cac6d3197620520f.tar.gz
Implement retry logic
- The total number of retries for any type of error will be the maximum of the specified value for the retry type, and the specified total number of retries. So, if you specify total=5 and connect=3, and there are 4 retries, we will not raise a MaxRetryError. Alternatively, if you specify total=3 and connect=5, we will also not raise a MaxRetryError. Please see the tests for a full description of the functionality of the class. - Adds a new successful_retry handler to dummyserver/handlers.py which throws a 418 the first time you call it and a 200 the second time. It uses a class-level defaultdict to store state; I'm not sure how else to implement this. - Adds docs for new features - Splits out util.py into submodules - I need to know what you want to do here because as written it would change from `urllib3.util.Timeout` into `urllib3.util.timeout.Timeout` which is not good. - I believe there are some API changes here. - some subclasses of httplib.HTTPException can actually be raised as connection errors because they imply the request never actually got sent to the server. - urlopen previously would retry on read timeouts, which violated the urlopen contract (as I understood it) of only retrying things that couldn't possibly be side effecting. this code does not retry read timeouts by default. I am also testing this in two new environments - in the office which places my IP on 10.* subnet and I think has weird/different behavior when connecting to TARPIT_HOST than do standard wifi networks, and without an Internet connection, in which case a bunch of tests fail. Also, it's difficult to test some of these exceptional cases because the errors raised rely on the network stack, which (I think) is why the tests are failing on the branch. I'm still looking into it. Either way I am losing some confidence in the connection timeout tests; getting a network to reliably generate ECONNREFUSED, or not generate it and tie up a connection, is tricky, unless we want to go down a path like this: http://bugs.python.org/file26890/test_timeout.patch Ports in find_unused_port from test_support! This will be super useful in the future.
Diffstat (limited to 'dummyserver')
-rw-r--r--dummyserver/handlers.py28
-rw-r--r--dummyserver/testcase.py7
2 files changed, 31 insertions, 4 deletions
diff --git a/dummyserver/handlers.py b/dummyserver/handlers.py
index 5d6e2e6b..72faa1aa 100644
--- a/dummyserver/handlers.py
+++ b/dummyserver/handlers.py
@@ -1,5 +1,6 @@
from __future__ import print_function
+import collections
import gzip
import json
import logging
@@ -41,11 +42,12 @@ class TestingApp(WSGIHandler):
Simple app that performs various operations, useful for testing an HTTP
library.
- Given any path, it will attempt to convert it will load a corresponding
- local method if it exists. Status code 200 indicates success, 400 indicates
- failure. Each method has its own conditions for success/failure.
+ Given any path, it will attempt to load a corresponding local method if
+ it exists. Status code 200 indicates success, 400 indicates failure. Each
+ method has its own conditions for success/failure.
"""
def __call__(self, environ, start_response):
+ """ Call the correct method in this class based on the incoming URI """
req = HTTPRequest(environ)
req.params = {}
@@ -172,6 +174,25 @@ class TestingApp(WSGIHandler):
def headers(self, request):
return Response(json.dumps(request.headers))
+ def successful_retry(self, request):
+ """ Handler which will return an error and then success
+
+ It's not currently very flexible as the number of retries is hard-coded.
+ """
+ test_name = request.headers.get('test-name', None)
+ if not test_name:
+ return Response("test-name header not set",
+ status="400 Bad Request")
+
+ if not hasattr(self, 'retry_test_names'):
+ self.retry_test_names = collections.defaultdict(int)
+ self.retry_test_names[test_name] += 1
+
+ if self.retry_test_names[test_name] >= 2:
+ return Response("Retry successful!")
+ else:
+ return Response("need to keep retrying!", status="418 I'm A Teapot")
+
def shutdown(self, request):
sys.exit()
@@ -207,7 +228,6 @@ def _parse_header(line):
params.pop(0) # get rid of the dummy again
pdict = {}
for name, value in params:
- print(repr(value))
value = email.utils.collapse_rfc2231_value(value)
if len(value) >= 2 and value[0] == '"' and value[-1] == '"':
value = value[1:-1]
diff --git a/dummyserver/testcase.py b/dummyserver/testcase.py
index 35769efb..335b2f2d 100644
--- a/dummyserver/testcase.py
+++ b/dummyserver/testcase.py
@@ -40,6 +40,13 @@ class SocketDummyServerTestCase(unittest.TestCase):
class HTTPDummyServerTestCase(unittest.TestCase):
+ """ A simple HTTP server that runs when your test class runs
+
+ Have your unittest class inherit from this one, and then a simple server
+ will start when your tests run, and automatically shut down when they
+ complete. For examples of what test requests you can send to the server,
+ see the TestingApp in dummyserver/handlers.py.
+ """
scheme = 'http'
host = 'localhost'
host_alt = '127.0.0.1' # Some tests need two hosts