1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Refs #28948 -- Precomputed once serialized cookie messages.

When the cookie size is too long, the same messages were serialized
over and over again.
This commit is contained in:
David Wobrock 2023-02-27 09:07:02 +01:00 committed by Mariusz Felisiak
parent 67208a5ad6
commit 9d0c878abf
2 changed files with 44 additions and 15 deletions

View File

@ -47,14 +47,28 @@ class MessageDecoder(json.JSONDecoder):
return self.process_messages(decoded)
class MessageSerializer:
class MessagePartSerializer:
def dumps(self, obj):
return json.dumps(
obj,
separators=(",", ":"),
cls=MessageEncoder,
).encode("latin-1")
return [
json.dumps(
o,
separators=(",", ":"),
cls=MessageEncoder,
)
for o in obj
]
class MessagePartGatherSerializer:
def dumps(self, obj):
"""
The parameter is an already serialized list of Message objects. No need
to serialize it again, only join the list together and encode it.
"""
return ("[" + ",".join(obj) + "]").encode("latin-1")
class MessageSerializer:
def loads(self, data):
return json.loads(data.decode("latin-1"), cls=MessageDecoder)
@ -70,6 +84,7 @@ class CookieStorage(BaseStorage):
# restrict the session cookie to 1/2 of 4kb. See #18781.
max_cookie_size = 2048
not_finished = "__messagesnotfinished__"
not_finished_json = json.dumps("__messagesnotfinished__")
key_salt = "django.contrib.messages"
def __init__(self, *args, **kwargs):
@ -122,7 +137,8 @@ class CookieStorage(BaseStorage):
returned), and add the not_finished sentinel value to indicate as much.
"""
unstored_messages = []
encoded_data = self._encode(messages)
serialized_messages = MessagePartSerializer().dumps(messages)
encoded_data = self._encode_parts(serialized_messages)
if self.max_cookie_size:
# data is going to be stored eventually by SimpleCookie, which
# adds its own overhead, which we must account for.
@ -134,27 +150,40 @@ class CookieStorage(BaseStorage):
while encoded_data and stored_length(encoded_data) > self.max_cookie_size:
if remove_oldest:
unstored_messages.append(messages.pop(0))
serialized_messages.pop(0)
else:
unstored_messages.insert(0, messages.pop())
encoded_data = self._encode(
messages + [self.not_finished], encode_empty=unstored_messages
serialized_messages.pop()
encoded_data = self._encode_parts(
serialized_messages + [self.not_finished_json],
encode_empty=bool(unstored_messages),
)
self._update_cookie(encoded_data, response)
return unstored_messages
def _encode(self, messages, encode_empty=False):
def _encode_parts(self, messages, encode_empty=False):
"""
Return an encoded version of the messages list which can be stored as
plain text.
Return an encoded version of the serialized messages list which can be
stored as plain text.
Since the data will be retrieved from the client-side, the encoded data
also contains a hash to ensure that the data was not tampered with.
"""
if messages or encode_empty:
return self.signer.sign_object(
messages, serializer=MessageSerializer, compress=True
messages, serializer=MessagePartGatherSerializer, compress=True
)
def _encode(self, messages, encode_empty=False):
"""
Return an encoded version of the messages list which can be stored as
plain text.
Proxies MessagePartSerializer.dumps and _encoded_parts.
"""
serialized_messages = MessagePartSerializer().dumps(messages)
return self._encode_parts(serialized_messages, encode_empty=encode_empty)
def _decode(self, data):
"""
Safely decode an encoded text stream back into a list of messages.

View File

@ -60,9 +60,9 @@ class CookieTests(BaseTests, SimpleTestCase):
def encode_decode(self, *args, **kwargs):
storage = self.get_storage()
message = Message(constants.DEBUG, *args, **kwargs)
message = [Message(constants.DEBUG, *args, **kwargs)]
encoded = storage._encode(message)
return storage._decode(encoded)
return storage._decode(encoded)[0]
def test_get(self):
storage = self.storage_class(self.get_request())