Files
flatpak/tests/http-utils-test-server.py
T
Razze 3a26b16761 http: Retry dropped and truncated responses
Curl's partial-file, empty-reply and send/receive errors were mapped to
G_IO_ERROR_FAILED, so the existing transient-error retry policy skipped
connection losses. Map them to partial-input and connection-closed errors
without changing the retry limit or retrying streams already written to.

Test recovery from empty and truncated replies, including compressed
cache output, and verify no partial contents survive the retry.
2026-10-12 10:52:23 +00:00

128 lines
3.9 KiB
Python
Executable File

#!/usr/bin/python3
from wsgiref.handlers import format_date_time
from email.utils import parsedate
from calendar import timegm
import gzip
import sys
import time
import zlib
import os
from urllib.parse import parse_qs
import http.server as http_server
from io import BytesIO
server_start_time = int(time.time())
def parse_http_date(date):
parsed = parsedate(date)
if parsed is not None:
return timegm(parsed)
else:
return None
class RequestHandler(http_server.BaseHTTPRequestHandler):
attempts = {}
def do_GET(self):
parts = self.path.split('?', 1)
path = parts[0]
if len(parts) == 1:
query = {}
else:
query = parse_qs(parts[1], keep_blank_values=True)
response = 200
add_headers = {}
attempt = self.attempts.get(self.path, 0)
self.attempts[self.path] = attempt + 1
if attempt == 0 and path == '/empty-reply':
self.close_connection = True
return
if attempt == 0 and path == '/partial-reply':
self.send_response(200)
self.send_header('Content-Length', '100')
self.end_headers()
self.wfile.write(b'incomplete')
self.close_connection = True
return
if 'modified-time' in query:
modified_since = self.headers.get("If-Modified-Since")
if modified_since:
modified_since_time = parse_http_date(modified_since)
if modified_since_time <= server_start_time:
response = 304
add_headers["Last-Modified"] = format_date_time(server_start_time)
if 'etag' in query:
etag = str(server_start_time)
if self.headers.get("If-None-Match") == etag:
response = 304
add_headers['Etag'] = etag
self.send_response(response)
for k, v in list(add_headers.items()):
self.send_header(k, v)
if 'max-age' in query:
self.send_header('Cache-Control', 'max-age=' + query['max-age'][0])
if 'no-cache' in query:
self.send_header('Cache-Control', 'no-cache')
if 'expires-past' in query:
self.send_header('Expires', format_date_time(server_start_time - 3600))
if 'expires-future' in query:
self.send_header('Expires', format_date_time(server_start_time + 3600))
if response == 200:
self.send_header("Content-Type", "text/plain; charset=UTF-8")
contents = "path=" + self.path + "\n"
if not 'ignore-accept-encoding' in query:
accept_encoding = self.headers.get("Accept-Encoding")
if accept_encoding and accept_encoding == 'gzip':
self.send_header("Content-Encoding", "gzip")
buf = BytesIO()
gzfile = gzip.GzipFile(mode='wb', fileobj=buf)
if isinstance(contents, bytes):
gzfile.write(contents)
else:
gzfile.write(contents.encode('utf-8'))
gzfile.close()
contents = buf.getvalue()
self.end_headers()
if response == 200:
if isinstance(contents, bytes):
self.wfile.write(contents)
else:
self.wfile.write(contents.encode('utf-8'))
def run(dir):
RequestHandler.protocol_version = "HTTP/1.0"
httpd = http_server.HTTPServer( ("127.0.0.1", 0), RequestHandler)
host, port = httpd.socket.getsockname()[:2]
with open("httpd-port", 'w') as file:
file.write("%d" % port)
try:
os.write(3, bytes("Started\n", 'utf-8'));
except:
pass
print("Serving HTTP on port %d" % port);
if dir:
os.chdir(dir)
httpd.serve_forever()
if __name__ == '__main__':
dir = None
if len(sys.argv) >= 2 and len(sys.argv[1]) > 0:
dir = sys.argv[1]
run(dir)