Issue 2551304
Created on 2023-12-11 17:23 by rouilj, last changed 2026-08-03 05:32 by rouilj.
| msg7872 |
Author: [hidden] (rouilj) |
Date: 2023-12-11 17:23 |
|
It looks like the XML-RPC endpoint doesn't have collision detection. This can lead to lost
edits.
Since xml-rpc is done over http, if-match and etag headers should work to prevent this.
However any existing clients will have to be updated to use it.
I don't see anything in the xmlrpc spec that is designed to prevent this.
Possible sequence that results in lost data:
user 1 xmlrpc display issue1 status is open
user 2 xmlrpc display issue1 status is open
user 2 modifies status to closed via set call
user 1 modifies status to new via set call
user2's changes are lost.
|
| msg8488 |
Author: [hidden] (rouilj) |
Date: 2026-08-03 05:32 |
|
I wonder if there are enough users to make fixing this worthwhile.
Maybe it's time to deprecate xml-rpc endpoint since we have rest.
We need to send etag on display() and require if-match on set().
It looks like you have to subclass the transport. Some google AI gave me this to record
the headers and make them available. Note this will overwrite the headers on each call.
So threads will each need a unique instance of the class.
import xmlrpc.client
class HeaderCapturingTransport(xmlrpc.client.Transport):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# This will hold the response headers from the most recent request
self.last_headers = None
def single_request(self, host, handler, request_body, verbose=False):
# 1. Establish the HTTP connection using standard internals
connection = self.make_connection(host)
try:
if verbose:
connection.set_debuglevel(1)
# 2. Send request headers and content
self.send_request(connection, handler, request_body, debug=verbose)
self.send_content(connection, request_body)
# 3. Intercept the HTTP response
response = connection.getresponse()
# 4. CAPTURE HEADERS: Parse and save them to the transport instance
# response.headers is an http.client.HTTPMessage object
self.last_headers = response.headers
# 5. Handle the response payload normally using standard validation
if response.status == 200:
return self.parse_response(response)
# Handle non-200 responses appropriately
raise xmlrpc.client.ProtocolError(
host + handler,
response.status,
response.reason,
response.headers
)
finally:
self.close()
# Initialize the custom transport
transport = HeaderCapturingTransport()
# Bind the transport to your ServerProxy
proxy = xmlrpc.client.ServerProxy('http://localhost:8000', transport=transport)
try:
# Make your normal RPC call
result = proxy.some_remote_function()
print("RPC Result:", result)
# Read the response headers captured during the call
headers = transport.last_headers
if headers:
print("\n--- Response Headers ---")
# Access a specific header safely (case-insensitive)
print("Server Type:", headers.get("Server"))
print("Content Type:", headers.get("Content-Type"))
# Iterate over all headers
for key, value in headers.items():
print(f"{key}: {value}")
except xmlrpc.client.Error as err:
print(f"XML-RPC Error: {err}")
Also update the xmlrpc docs. It still has some python2 constructs.
|
|
| Date |
User |
Action |
Args |
| 2026-08-03 05:32:44 | rouilj | set | messages:
+ msg8488 |
| 2023-12-11 17:23:35 | rouilj | create | |
|