Fixed #29186 -- Fixed pickling HttpRequest and subclasses.

This commit is contained in:
Anvesh Mishra 2022-08-09 19:45:07 +05:30 committed by Mariusz Felisiak
parent e14d08cd89
commit 6220c445c4
4 changed files with 42 additions and 2 deletions

View File

@ -64,6 +64,9 @@ class LimitedStream:
class WSGIRequest(HttpRequest): class WSGIRequest(HttpRequest):
non_picklable_attrs = HttpRequest.non_picklable_attrs | frozenset(["environ"])
meta_non_picklable_attrs = frozenset(["wsgi.errors", "wsgi.input"])
def __init__(self, environ): def __init__(self, environ):
script_name = get_script_name(environ) script_name = get_script_name(environ)
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a # If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
@ -89,6 +92,13 @@ class WSGIRequest(HttpRequest):
self._read_started = False self._read_started = False
self.resolver_match = None self.resolver_match = None
def __getstate__(self):
state = super().__getstate__()
for attr in self.meta_non_picklable_attrs:
if attr in state["META"]:
del state["META"][attr]
return state
def _get_scheme(self): def _get_scheme(self):
return self.environ.get("wsgi.url_scheme") return self.environ.get("wsgi.url_scheme")

View File

@ -51,6 +51,8 @@ class HttpRequest:
_encoding = None _encoding = None
_upload_handlers = [] _upload_handlers = []
non_picklable_attrs = frozenset(["resolver_match", "_stream"])
def __init__(self): def __init__(self):
# WARNING: The `WSGIRequest` subclass doesn't call `super`. # WARNING: The `WSGIRequest` subclass doesn't call `super`.
# Any variable assignment made here should also happen in # Any variable assignment made here should also happen in
@ -78,6 +80,21 @@ class HttpRequest:
self.get_full_path(), self.get_full_path(),
) )
def __getstate__(self):
obj_dict = self.__dict__.copy()
for attr in self.non_picklable_attrs:
if attr in obj_dict:
del obj_dict[attr]
return obj_dict
def __deepcopy__(self, memo):
obj = copy.copy(self)
for attr in self.non_picklable_attrs:
if hasattr(self, attr):
setattr(obj, attr, copy.deepcopy(getattr(self, attr), memo))
memo[id(self)] = obj
return obj
@cached_property @cached_property
def headers(self): def headers(self):
return HttpHeaders(self.META) return HttpHeaders(self.META)

View File

@ -370,12 +370,10 @@ class HttpResponse(HttpResponseBase):
[ [
"resolver_match", "resolver_match",
# Non-picklable attributes added by test clients. # Non-picklable attributes added by test clients.
"asgi_request",
"client", "client",
"context", "context",
"json", "json",
"templates", "templates",
"wsgi_request",
] ]
) )

View File

@ -1,3 +1,4 @@
import pickle
from io import BytesIO from io import BytesIO
from itertools import chain from itertools import chain
from urllib.parse import urlencode from urllib.parse import urlencode
@ -669,6 +670,20 @@ class RequestsTests(SimpleTestCase):
with self.assertRaises(UnreadablePostError): with self.assertRaises(UnreadablePostError):
request.FILES request.FILES
def test_pickling_request(self):
request = HttpRequest()
request.method = "GET"
request.path = "/testpath/"
request.META = {
"QUERY_STRING": ";some=query&+query=string",
"SERVER_NAME": "example.com",
"SERVER_PORT": 80,
}
request.COOKIES = {"post-key": "post-value"}
dump = pickle.dumps(request)
request_from_pickle = pickle.loads(dump)
self.assertEqual(repr(request), repr(request_from_pickle))
class HostValidationTests(SimpleTestCase): class HostValidationTests(SimpleTestCase):
poisoned_hosts = [ poisoned_hosts = [