You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2021/12/14 11:26:53 UTC

svn commit: r1895945 [4/4] - in /httpd/httpd/branches/2.4.x: ./ test/ test/modules/http2/ test/modules/http2/conf/ test/modules/http2/data/ test/modules/http2/htdocs/ test/modules/http2/htdocs/cgi/ test/modules/http2/htdocs/test1/ test/modules/http2/ht...

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/forbidden.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/forbidden.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/forbidden.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/forbidden.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1,11 @@
+<html>
+    <head>
+        <title>403 - Forbidden</title>
+    </head>
+    <body>
+        <h1>403 - Forbidden</h1>
+        <p>
+            An example of an error document.
+        </p>
+    </body>
+</html>

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/index.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/index.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/index.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/index.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1,9 @@
+<html>
+    <head>
+        <title>mod_h2 test site generic</title>
+    </head>
+    <body>
+        <h1>mod_h2 test site generic</h1>
+    </body>
+</html>
+

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/001.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/001.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/001.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/001.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML> 
+ <html>
+   <head>
+     <title>HTML/2.0 Test File: 001</title>
+   </head>
+   <body>
+     <p><h1>HTML/2.0 Test File: 001</h1></p>
+     <p>This file only contains a simple HTML structure with plain text.</p>
+   </body>
+</html>

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/002.jpg
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/002.jpg?rev=1895945&view=auto
==============================================================================
Binary file - no diff available.

Propchange: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/002.jpg
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/003.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/003.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/003.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/003.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML> 
+ <html>
+   <head>
+     <title>HTML/2.0 Test File: 003</title>
+   </head>
+   <body>
+     <p><h1>HTML/2.0 Test File: 003</h1></p>
+     <p>This is a text HTML file with a big image:</p>
+	 <p><img src="003/003_img.jpg" alt="GSMA Logo" style="width:269px;height:249px"></p>
+   </body>
+</html>

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/003/003_img.jpg
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/003/003_img.jpg?rev=1895945&view=auto
==============================================================================
Binary file - no diff available.

Propchange: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/003/003_img.jpg
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/004.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/004.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/004.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/004.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1,23 @@
+<html>
+	<head>
+		<title>HTML/2.0 Test File: 004</title>
+	</head>
+	<body>
+		<p><h1>HTML/2.0 Test File: 004</h1>
+		This file contains plain text with a bunch of images.<br>
+		<img src="004/gophertiles_142.jpg" height="32" width="32"><img src="004/gophertiles_084.jpg" height="32" width="32"><img src="004/gophertiles_052.jpg" height="32" width="32"><img src="004/gophertiles_077.jpg" height="32" width="32"><img src="004/gophertiles_030.jpg" height="32" width="32"><img src="004/gophertiles_027.jpg" height="32" width="32"><img src="004/gophertiles_039.jpg" height="32" width="32"><img src="004/gophertiles_025.jpg" height="32" width="32"><img src="004/gophertiles_017.jpg" height="32" width="32"><img src="004/gophertiles_179.jpg" height="32" width="32"><img src="004/gophertiles_032.jpg" height="32" width="32"><img src="004/gophertiles_161.jpg" height="32" width="32"><img src="004/gophertiles_088.jpg" height="32" width="32"><img src="004/gophertiles_022.jpg" height="32" width="32"><img src="004/gophertiles_146.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_102.jpg" height="32" width="32"><img src="004/gophertiles_009.jpg" height="32" width="32"><img src="004/gophertiles_132.jpg" height="32" width="32"><img src="004/gophertiles_137.jpg" height="32" width="32"><img src="004/gophertiles_055.jpg" height="32" width="32"><img src="004/gophertiles_036.jpg" height="32" width="32"><img src="004/gophertiles_127.jpg" height="32" width="32"><img src="004/gophertiles_145.jpg" height="32" width="32"><img src="004/gophertiles_147.jpg" height="32" width="32"><img src="004/gophertiles_153.jpg" height="32" width="32"><img src="004/gophertiles_105.jpg" height="32" width="32"><img src="004/gophertiles_103.jpg" height="32" width="32"><img src="004/gophertiles_033.jpg" height="32" width="32"><img src="004/gophertiles_054.jpg" height="32" width="32"><img src="004/gophertiles_015.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_016.jpg" height="32" width="32"><img src="004/gophertiles_072.jpg" height="32" width="32"><img src="004/gophertiles_115.jpg" height="32" width="32"><img src="004/gophertiles_108.jpg" height="32" width="32"><img src="004/gophertiles_148.jpg" height="32" width="32"><img src="004/gophertiles_070.jpg" height="32" width="32"><img src="004/gophertiles_083.jpg" height="32" width="32"><img src="004/gophertiles_118.jpg" height="32" width="32"><img src="004/gophertiles_053.jpg" height="32" width="32"><img src="004/gophertiles_021.jpg" height="32" width="32"><img src="004/gophertiles_059.jpg" height="32" width="32"><img src="004/gophertiles_130.jpg" height="32" width="32"><img src="004/gophertiles_163.jpg" height="32" width="32"><img src="004/gophertiles_098.jpg" height="32" width="32"><img src="004/gophertiles_064.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_018.jpg" height="32" width="32"><img src="004/gophertiles_058.jpg" height="32" width="32"><img src="004/gophertiles_167.jpg" height="32" width="32"><img src="004/gophertiles_082.jpg" height="32" width="32"><img src="004/gophertiles_056.jpg" height="32" width="32"><img src="004/gophertiles_180.jpg" height="32" width="32"><img src="004/gophertiles_046.jpg" height="32" width="32"><img src="004/gophertiles_093.jpg" height="32" width="32"><img src="004/gophertiles_106.jpg" height="32" width="32"><img src="004/gophertiles_065.jpg" height="32" width="32"><img src="004/gophertiles_175.jpg" height="32" width="32"><img src="004/gophertiles_139.jpg" height="32" width="32"><img src="004/gophertiles_101.jpg" height="32" width="32"><img src="004/gophertiles_099.jpg" height="32" width="32"><img src="004/gophertiles_051.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_140.jpg" height="32" width="32"><img src="004/gophertiles_134.jpg" height="32" width="32"><img src="004/gophertiles_149.jpg" height="32" width="32"><img src="004/gophertiles_049.jpg" height="32" width="32"><img src="004/gophertiles_095.jpg" height="32" width="32"><img src="004/gophertiles_075.jpg" height="32" width="32"><img src="004/gophertiles_066.jpg" height="32" width="32"><img src="004/gophertiles_090.jpg" height="32" width="32"><img src="004/gophertiles_035.jpg" height="32" width="32"><img src="004/gophertiles_114.jpg" height="32" width="32"><img src="004/gophertiles_160.jpg" height="32" width="32"><img src="004/gophertiles_079.jpg" height="32" width="32"><img src="004/gophertiles_062.jpg" height="32" width="32"><img src="004/gophertiles_096.jpg" height="32" width="32"><img src="004/gophertiles_100.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_104.jpg" height="32" width="32"><img src="004/gophertiles_057.jpg" height="32" width="32"><img src="004/gophertiles_037.jpg" height="32" width="32"><img src="004/gophertiles_086.jpg" height="32" width="32"><img src="004/gophertiles_168.jpg" height="32" width="32"><img src="004/gophertiles_138.jpg" height="32" width="32"><img src="004/gophertiles_045.jpg" height="32" width="32"><img src="004/gophertiles_141.jpg" height="32" width="32"><img src="004/gophertiles_029.jpg" height="32" width="32"><img src="004/gophertiles_165.jpg" height="32" width="32"><img src="004/gophertiles_110.jpg" height="32" width="32"><img src="004/gophertiles_063.jpg" height="32" width="32"><img src="004/gophertiles_158.jpg" height="32" width="32"><img src="004/gophertiles_122.jpg" height="32" width="32"><img src="004/gophertiles_068.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_170.jpg" height="32" width="32"><img src="004/gophertiles_120.jpg" height="32" width="32"><img src="004/gophertiles_117.jpg" height="32" width="32"><img src="004/gophertiles_031.jpg" height="32" width="32"><img src="004/gophertiles_113.jpg" height="32" width="32"><img src="004/gophertiles_074.jpg" height="32" width="32"><img src="004/gophertiles_129.jpg" height="32" width="32"><img src="004/gophertiles_019.jpg" height="32" width="32"><img src="004/gophertiles_060.jpg" height="32" width="32"><img src="004/gophertiles_109.jpg" height="32" width="32"><img src="004/gophertiles_080.jpg" height="32" width="32"><img src="004/gophertiles_097.jpg" height="32" width="32"><img src="004/gophertiles_116.jpg" height="32" width="32"><img src="004/gophertiles_085.jpg" height="32" width="32"><img src="004/gophertiles_050.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_151.jpg" height="32" width="32"><img src="004/gophertiles_094.jpg" height="32" width="32"><img src="004/gophertiles_067.jpg" height="32" width="32"><img src="004/gophertiles_128.jpg" height="32" width="32"><img src="004/gophertiles_034.jpg" height="32" width="32"><img src="004/gophertiles_135.jpg" height="32" width="32"><img src="004/gophertiles_012.jpg" height="32" width="32"><img src="004/gophertiles_010.jpg" height="32" width="32"><img src="004/gophertiles_152.jpg" height="32" width="32"><img src="004/gophertiles_171.jpg" height="32" width="32"><img src="004/gophertiles_087.jpg" height="32" width="32"><img src="004/gophertiles_126.jpg" height="32" width="32"><img src="004/gophertiles_048.jpg" height="32" width="32"><img src="004/gophertiles_023.jpg" height="32" width="32"><img src="004/gophertiles_078.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_071.jpg" height="32" width="32"><img src="004/gophertiles_131.jpg" height="32" width="32"><img src="004/gophertiles_073.jpg" height="32" width="32"><img src="004/gophertiles_143.jpg" height="32" width="32"><img src="004/gophertiles_173.jpg" height="32" width="32"><img src="004/gophertiles_154.jpg" height="32" width="32"><img src="004/gophertiles_061.jpg" height="32" width="32"><img src="004/gophertiles_178.jpg" height="32" width="32"><img src="004/gophertiles_013.jpg" height="32" width="32"><img src="004/gophertiles_028.jpg" height="32" width="32"><img src="004/gophertiles_157.jpg" height="32" width="32"><img src="004/gophertiles_038.jpg" height="32" width="32"><img src="004/gophertiles_069.jpg" height="32" width="32"><img src="004/gophertiles_174.jpg" height="32" width="32"><img src="004/gophertiles_076.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_155.jpg" height="32" width="32"><img src="004/gophertiles_107.jpg" height="32" width="32"><img src="004/gophertiles_136.jpg" height="32" width="32"><img src="004/gophertiles_144.jpg" height="32" width="32"><img src="004/gophertiles_091.jpg" height="32" width="32"><img src="004/gophertiles_024.jpg" height="32" width="32"><img src="004/gophertiles_014.jpg" height="32" width="32"><img src="004/gophertiles_159.jpg" height="32" width="32"><img src="004/gophertiles_011.jpg" height="32" width="32"><img src="004/gophertiles_176.jpg" height="32" width="32"><img src="004/gophertiles_162.jpg" height="32" width="32"><img src="004/gophertiles_156.jpg" height="32" width="32"><img src="004/gophertiles_081.jpg" height="32" width="32"><img src="004/gophertiles_119.jpg" height="32" width="32"><img src="004/gophertiles_026.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_133.jpg" height="32" width="32"><img src="004/gophertiles_020.jpg" height="32" width="32"><img src="004/gophertiles_044.jpg" height="32" width="32"><img src="004/gophertiles_125.jpg" height="32" width="32"><img src="004/gophertiles_150.jpg" height="32" width="32"><img src="004/gophertiles_172.jpg" height="32" width="32"><img src="004/gophertiles_002.jpg" height="32" width="32"><img src="004/gophertiles_169.jpg" height="32" width="32"><img src="004/gophertiles_007.jpg" height="32" width="32"><img src="004/gophertiles_008.jpg" height="32" width="32"><img src="004/gophertiles_042.jpg" height="32" width="32"><img src="004/gophertiles_041.jpg" height="32" width="32"><img src="004/gophertiles_166.jpg" height="32" width="32"><img src="004/gophertiles_005.jpg" height="32" width="32"><img src="004/gophertiles_089.jpg" height="32" width="32"><br>
+		<img src="004/gophertiles_177.jpg" height="32" width="32"><img src="004/gophertiles_092.jpg" height="32" width="32"><img src="004/gophertiles_043.jpg" height="32" width="32"><img src="004/gophertiles_111.jpg" height="32" width="32"><img src="004/gophertiles_047.jpg" height="32" width="32"><img src="004/gophertiles.jpg" height="32" width="32"><img src="004/gophertiles_006.jpg" height="32" width="32"><img src="004/gophertiles_121.jpg" height="32" width="32"><img src="004/gophertiles_004.jpg" height="32" width="32"><img src="004/gophertiles_124.jpg" height="32" width="32"><img src="004/gophertiles_123.jpg" height="32" width="32"><img src="004/gophertiles_112.jpg" height="32" width="32"><img src="004/gophertiles_040.jpg" height="32" width="32"><img src="004/gophertiles_164.jpg" height="32" width="32"><img src="004/gophertiles_003.jpg" height="32" width="32"><br>
+		<hr>This page is developed using this template:<a href="https://http2.golang.org/">HTTP/2 demo server</a>
+		</p>
+	</body>
+</html>
\ No newline at end of file

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/004/gophertiles.jpg
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/004/gophertiles.jpg?rev=1895945&view=auto
==============================================================================
Binary file - no diff available.

Propchange: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/004/gophertiles.jpg
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML> 
+ <html>
+   <head>
+     <title>HTML/2.0 Test File: 006</title>
+     <link rel="stylesheet" type="text/css" href="006/006.css">
+     <script type="text/javascript" src="006/006.js"></script>
+   </head>
+   <body>
+     <h1>HTML/2.0 Test File: 006</h1>
+     <div class="listTitle">This page contains:
+	     <ul class="listElements">
+			<li>HTML
+			<li>CSS
+			<li>JavaScript
+		</ul> 
+	</div>
+	<div class="listTitle">
+		<script type="text/javascript">
+		 mainJavascript();
+		</script>
+	</div>
+   </body>
+</html>
\ No newline at end of file

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/006.css
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/006.css?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/006.css (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/006.css Tue Dec 14 11:26:52 2021
@@ -0,0 +1,21 @@
+@CHARSET "ISO-8859-1";
+body{
+	background:HoneyDew;
+}
+p{
+color:#0000FF;
+text-align:left;
+}
+
+h1{
+color:#FF0000;
+text-align:center;
+}
+
+.listTitle{
+	font-size:large;
+}
+
+.listElements{
+	color:#3366FF
+}
\ No newline at end of file

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/006.js
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/006.js?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/006.js (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/006.js Tue Dec 14 11:26:52 2021
@@ -0,0 +1,31 @@
+/**
+ * JavaScript Functions File
+ */
+function returnDate()
+{
+  var currentDate;
+  currentDate=new Date();
+  var dateString=(currentDate.getMonth()+1)+'/'+currentDate.getDate()+'/'+currentDate.getFullYear();
+  return dateString;
+}
+
+function returnHour()
+{
+  var currentDate;
+  currentDate=new Date();
+  var hourString=currentDate.getHours()+':'+currentDate.getMinutes()+':'+currentDate.getSeconds();
+  return hourString; 
+}
+
+function javaScriptMessage(){
+	return 'This section is generated under JavaScript:<br>';
+}
+
+function mainJavascript(){
+	document.write(javaScriptMessage())
+	document.write('<ul class="listElements">');
+	document.write('<li>Current date (dd/mm/yyyy): ' + returnDate());
+	document.write('<br>');	
+	document.write('<li>Current time (hh:mm:ss): '+returnHour());
+	document.write('</ul>');
+}
\ No newline at end of file

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/header.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/header.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/header.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/006/header.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1 @@
+<title>My Header Title</title>

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/007.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/007.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/007.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/007.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="ISO-8859-1">
+<title>HTML/2.0 Test File: 007</title>
+</head>
+<body>
+	<h1>HTML/2.0 Test File: 007</h1>
+    <div><p>This page is used to send data from the client to the server:</p>
+		<FORM ACTION="007/007.py" METHOD="post" ENCTYPE="multipart/form-data">
+			<input type="hidden" name="pageName" value="007.html">
+			Name:<input type="text" name="pName" value="Write your name here." size="30" maxlength="30"><br>
+			Age:<input type="text" name="pAge" value="00" size="2" maxlength="2"><br>
+			Gender: Male<input type="radio" name="pGender" VALUE="Male">
+					Female<input type="radio" name="pGender" VALUE="Female"><br>
+			<input type="submit" name="userForm" value="Send">
+			<input type="reset" value="Clear">
+		</FORM> 
+	</div>
+</body>
+</html>
\ No newline at end of file

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/007/007.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/007/007.py?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/007/007.py (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/007/007.py Tue Dec 14 11:26:52 2021
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import cgi, sys
+import cgitb; cgitb.enable()
+
+print "Content-Type: text/html;charset=UTF-8"
+print
+
+print """\
+	<!DOCTYPE html><html><head>
+	<title>HTML/2.0 Test File: 007 (received data)</title></head>
+	<body><h1>HTML/2.0 Test File: 007</h1>"""
+
+# alternative output: parsed form params <-> plain POST body
+parseContent = True		# <-> False
+
+if parseContent:
+	print '<h2>Data processed:</h2><ul>'
+	form = cgi.FieldStorage()
+	for name in form:
+		print '<li>', name, ': ', form[name].value, '</li>'
+	print '</ul>'
+else:
+	print '<h2>POST data output:</h2><div><pre>'
+	data = sys.stdin.read()
+	print data
+	print '</pre></div>'
+	
+print '</body></html>'
\ No newline at end of file

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/009.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/009.py?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/009.py (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/009.py Tue Dec 14 11:26:52 2021
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import cgi, sys, time
+import cgitb; cgitb.enable()
+
+print "Content-Type: text/html;charset=UTF-8"
+print
+
+print """\
+	<!DOCTYPE html><html><head>
+	<title>HTML/2.0 Test File: 009 (server time)</title></head>
+	<body><h1>HTML/2.0 Test File: 009</h1>
+    <p>60 seconds of server time, one by one.</p>"""
+
+for i in range(60):
+	s = time.strftime("%Y-%m-%d %H:%M:%S")
+	print "<div>", s, "</div>"
+	sys.stdout.flush()
+	time.sleep(1)
+
+print "<p>done.</p></body></html>"
\ No newline at end of file

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/alive.json
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/alive.json?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/alive.json (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/alive.json Tue Dec 14 11:26:52 2021
@@ -0,0 +1,5 @@
+{
+    "host" : "test1",
+    "alive" : true
+}
+

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/index.html
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/index.html?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/index.html (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test1/index.html Tue Dec 14 11:26:52 2021
@@ -0,0 +1,46 @@
+<html>
+    <head>
+        <title>mod_h2 test site</title>
+    </head>
+    <body>
+        <h1>mod_h2 test site</h1>
+        <p></p>
+        <h2>served directly</h2>
+        <ul>
+            <li><a href="001.html">01: html</a></li>
+            <li><a href="002.jpg">02: image</a></li>
+            <li><a href="003.html">03: html+image</a></li>
+            <li><a href="004.html">04: tiled image</a></li>
+            <li><a href="005.txt">05: large text</a></li>
+            <li><a href="006.html">06: html/js/css</a></li>
+            <li><a href="007.html">07: form submit</a></li>
+            <li><a href="upload.py">08: upload</a></li>
+            <li><a href="009.py">09: small chunks</a></li>
+        </ul>
+        <h2>mod_proxyied</h2>
+        <ul>
+            <li><a href="proxy/001.html">01: html</a></li>
+            <li><a href="proxy/002.jpg">02: image</a></li>
+            <li><a href="proxy/003.html">03: html+image</a></li>
+            <li><a href="proxy/004.html">04: tiled image</a></li>
+            <li><a href="proxy/005.txt">05: large text</a></li>
+            <li><a href="proxy/006.html">06: html/js/css</a></li>
+            <li><a href="proxy/007.html">07: form submit</a></li>
+            <li><a href="proxy/upload.py">08: upload</a></li>
+            <li><a href="proxy/009.py">09: small chunks</a></li>
+        </ul>
+        <h2>mod_rewritten</h2>
+        <ul>
+            <li><a href="rewrite/001.html">01: html</a></li>
+            <li><a href="rewrite/002.jpg">02: image</a></li>
+            <li><a href="rewrite/003.html">03: html+image</a></li>
+            <li><a href="rewrite/004.html">04: tiled image</a></li>
+            <li><a href="rewrite/005.txt">05: large text</a></li>
+            <li><a href="rewrite/006.html">06: html/js/css</a></li>
+            <li><a href="rewrite/007.html">07: form submit</a></li>
+            <li><a href="rewrite/upload.py">08: upload</a></li>
+            <li><a href="rewrite/009.py">09: small chunks</a></li>
+        </ul>
+    </body>
+</html>
+

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/006/006.css
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/006/006.css?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/006/006.css (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/006/006.css Tue Dec 14 11:26:52 2021
@@ -0,0 +1,21 @@
+@CHARSET "ISO-8859-1";
+body{
+	background:HoneyDew;
+}
+p{
+color:#0000FF;
+text-align:left;
+}
+
+h1{
+color:#FF0000;
+text-align:center;
+}
+
+.listTitle{
+	font-size:large;
+}
+
+.listElements{
+	color:#3366FF
+}
\ No newline at end of file

Propchange: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/006/006.css
------------------------------------------------------------------------------
    svn:executable = *

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/10%abnormal.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/10%25abnormal.txt?rev=1895945&view=auto
==============================================================================
    (empty)

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/alive.json
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/alive.json?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/alive.json (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/alive.json Tue Dec 14 11:26:52 2021
@@ -0,0 +1,4 @@
+{
+    "host" : "test2",
+    "alive" : true
+}

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/x%2f.test
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/htdocs/test2/x%252f.test?rev=1895945&view=auto
==============================================================================
    (empty)

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/log.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/log.py?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/log.py (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/log.py Tue Dec 14 11:26:52 2021
@@ -0,0 +1,163 @@
+import os
+import re
+import time
+from datetime import datetime, timedelta
+from io import SEEK_END
+from typing import List, Tuple, Any
+
+
+class HttpdErrorLog:
+    """Checking the httpd error log for errors and warnings, including
+       limiting checks from a last known position forward.
+    """
+
+    RE_ERRLOG_ERROR = re.compile(r'.*\[(?P<module>[^:]+):error].*')
+    RE_ERRLOG_WARN = re.compile(r'.*\[(?P<module>[^:]+):warn].*')
+    RE_APLOGNO = re.compile(r'.*\[(?P<module>[^:]+):(error|warn)].* (?P<aplogno>AH\d+): .+')
+    RE_SSL_LIB_ERR = re.compile(r'.*\[ssl:error].* SSL Library Error: error:(?P<errno>\S+):.+')
+
+    def __init__(self, path: str):
+        self._path = path
+        self._ignored_modules = []
+        self._ignored_lognos = set()
+        self._ignored_patterns = []
+        # remember the file position we started with
+        self._start_pos = 0
+        if os.path.isfile(self._path):
+            with open(self._path) as fd:
+                self._start_pos = fd.seek(0, SEEK_END)
+        self._last_pos = self._start_pos
+        self._last_errors = []
+        self._last_warnings = []
+        self._observed_erros = set()
+        self._observed_warnings = set()
+
+    def __repr__(self):
+        return f"HttpdErrorLog[{self._path}, errors: {' '.join(self._last_errors)}, " \
+               f"warnings: {' '.join(self._last_warnings)}]"
+
+    @property
+    def path(self) -> str:
+        return self._path
+
+    def clear_log(self):
+        if os.path.isfile(self.path):
+            os.remove(self.path)
+        self._start_pos = 0
+        self._last_pos = self._start_pos
+        self._last_errors = []
+        self._last_warnings = []
+        self._observed_erros = set()
+        self._observed_warnings = set()
+
+    def set_ignored_modules(self, modules: List[str]):
+        self._ignored_modules = modules.copy() if modules else []
+
+    def set_ignored_lognos(self, lognos: List[str]):
+        if lognos:
+            for l in lognos:
+                self._ignored_lognos.add(l)
+
+    def add_ignored_patterns(self, patterns: List[Any]):
+        self._ignored_patterns.extend(patterns)
+
+    def _is_ignored(self, line: str) -> bool:
+        for p in self._ignored_patterns:
+            if p.match(line):
+                return True
+        m = self.RE_APLOGNO.match(line)
+        if m and m.group('aplogno') in self._ignored_lognos:
+            return True
+        return False
+
+    def get_recent(self, advance=True) -> Tuple[List[str], List[str]]:
+        """Collect error and warning from the log since the last remembered position
+        :param advance: advance the position to the end of the log afterwards
+        :return: list of error and list of warnings as tuple
+        """
+        self._last_errors = []
+        self._last_warnings = []
+        if os.path.isfile(self._path):
+            with open(self._path) as fd:
+                fd.seek(self._last_pos, os.SEEK_SET)
+                for line in fd:
+                    if self._is_ignored(line):
+                        continue
+                    m = self.RE_ERRLOG_ERROR.match(line)
+                    if m and m.group('module') not in self._ignored_modules:
+                        self._last_errors.append(line)
+                        continue
+                    m = self.RE_ERRLOG_WARN.match(line)
+                    if m:
+                        if m and m.group('module') not in self._ignored_modules:
+                            self._last_warnings.append(line)
+                            continue
+                if advance:
+                    self._last_pos = fd.tell()
+            self._observed_erros.update(set(self._last_errors))
+            self._observed_warnings.update(set(self._last_warnings))
+        return self._last_errors, self._last_warnings
+
+    def get_recent_count(self, advance=True):
+        errors, warnings = self.get_recent(advance=advance)
+        return len(errors), len(warnings)
+
+    def ignore_recent(self):
+        """After a test case triggered errors/warnings on purpose, add
+           those to our 'observed' list so the do not get reported as 'missed'.
+           """
+        self._last_errors = []
+        self._last_warnings = []
+        if os.path.isfile(self._path):
+            with open(self._path) as fd:
+                fd.seek(self._last_pos, os.SEEK_SET)
+                for line in fd:
+                    if self._is_ignored(line):
+                        continue
+                    m = self.RE_ERRLOG_ERROR.match(line)
+                    if m and m.group('module') not in self._ignored_modules:
+                        self._observed_erros.add(line)
+                        continue
+                    m = self.RE_ERRLOG_WARN.match(line)
+                    if m:
+                        if m and m.group('module') not in self._ignored_modules:
+                            self._observed_warnings.add(line)
+                            continue
+                self._last_pos = fd.tell()
+
+    def get_missed(self) -> Tuple[List[str], List[str]]:
+        errors = []
+        warnings = []
+        if os.path.isfile(self._path):
+            with open(self._path) as fd:
+                fd.seek(self._start_pos, os.SEEK_SET)
+                for line in fd:
+                    if self._is_ignored(line):
+                        continue
+                    m = self.RE_ERRLOG_ERROR.match(line)
+                    if m and m.group('module') not in self._ignored_modules \
+                            and line not in self._observed_erros:
+                        errors.append(line)
+                        continue
+                    m = self.RE_ERRLOG_WARN.match(line)
+                    if m:
+                        if m and m.group('module') not in self._ignored_modules \
+                                and line not in self._observed_warnings:
+                            warnings.append(line)
+                            continue
+        return errors, warnings
+
+    def scan_recent(self, pattern: re, timeout=10):
+        if not os.path.isfile(self.path):
+            return False
+        with open(self.path) as fd:
+            end = datetime.now() + timedelta(seconds=timeout)
+            while True:
+                fd.seek(self._last_pos, os.SEEK_SET)
+                for line in fd:
+                    if pattern.match(line):
+                        return True
+                if datetime.now() > end:
+                    raise TimeoutError(f"pattern not found in error log after {timeout} seconds")
+                time.sleep(.1)
+        return False

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/nghttp.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/nghttp.py?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/nghttp.py (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/nghttp.py Tue Dec 14 11:26:52 2021
@@ -0,0 +1,289 @@
+import re
+import os
+import subprocess
+from datetime import datetime
+from typing import Dict
+
+from urllib.parse import urlparse
+
+from .result import ExecResult
+
+
+def _get_path(x):
+    return x["path"]
+    
+
+class Nghttp:
+
+    def __init__(self, path, connect_addr=None, tmp_dir="/tmp"):
+        self.NGHTTP = path
+        self.CONNECT_ADDR = connect_addr
+        self.TMP_DIR = tmp_dir
+
+    @staticmethod
+    def get_stream(streams, sid):
+        sid = int(sid)
+        if sid not in streams:
+            streams[sid] = {
+                    "id": sid,
+                    "header": {},
+                    "request": {
+                        "id": sid,
+                        "body": b''
+                    },
+                    "response": {
+                        "id": sid,
+                        "body": b''
+                    },
+                    "paddings": [],
+                    "promises": []
+            }
+        return streams[sid] if sid in streams else None
+
+    def run(self, urls, timeout, options):
+        return self._baserun(urls, timeout, options)
+
+    def complete_args(self, url, _timeout, options: [str]) -> [str]:
+        if not isinstance(url, list):
+            url = [url]
+        u = urlparse(url[0])
+        args = [self.NGHTTP]
+        if self.CONNECT_ADDR:
+            connect_host = self.CONNECT_ADDR
+            args.append("--header=host: %s:%s" % (u.hostname, u.port))
+        else:
+            connect_host = u.hostname
+        if options:
+            args.extend(options)
+        for xurl in url:
+            xu = urlparse(xurl)
+            nurl = "%s://%s:%s/%s" % (u.scheme, connect_host, xu.port, xu.path)
+            if xu.query:
+                nurl = "%s?%s" % (nurl, xu.query)
+            args.append(nurl)
+        return args
+
+    def _baserun(self, url, timeout, options):
+        return self._run(self.complete_args(url, timeout, options))
+    
+    def parse_output(self, btext) -> Dict:
+        # getting meta data and response body out of nghttp's output
+        # is a bit tricky. Without '-v' we just get the body. With '-v' meta
+        # data and timings in both directions are listed. 
+        # We rely on response :status: to be unique and on 
+        # response body not starting with space.
+        # Something not good enough for general purpose, but for these tests.
+        output = {}
+        body = ''
+        streams = {}
+        skip_indents = True
+        # chunk output into lines. nghttp mixes text
+        # meta output with bytes from the response body.
+        lines = [l.decode() for l in btext.split(b'\n')]
+        for lidx, l in enumerate(lines):
+            if len(l) == 0:
+                body += '\n'
+                continue
+            m = re.match(r'\[.*] recv \(stream_id=(\d+)\) (\S+): (\S*)', l)
+            if m:
+                s = self.get_stream(streams, m.group(1))
+                hname = m.group(2)
+                hval = m.group(3)
+                print("stream %d header %s: %s" % (s["id"], hname, hval))
+                header = s["header"]
+                if hname in header: 
+                    header[hname] += ", %s" % hval
+                else:
+                    header[hname] = hval
+                body = ''
+                continue
+
+            m = re.match(r'\[.*] recv HEADERS frame <.* stream_id=(\d+)>', l)
+            if m:
+                s = self.get_stream(streams, m.group(1))
+                if s:
+                    print("stream %d: recv %d header" % (s["id"], len(s["header"]))) 
+                    response = s["response"]
+                    hkey = "header"
+                    if "header" in response:
+                        h = response["header"]
+                        if ":status" in h and int(h[":status"]) >= 200:
+                            hkey = "trailer"
+                        else:
+                            prev = {
+                                "header": h
+                            }
+                            if "previous" in response:
+                                prev["previous"] = response["previous"]
+                            response["previous"] = prev
+                    response[hkey] = s["header"]
+                    s["header"] = {} 
+                body = ''
+                continue
+            
+            m = re.match(r'(.*)\[.*] recv DATA frame <length=(\d+), .*stream_id=(\d+)>', l)
+            if m:
+                s = self.get_stream(streams, m.group(3))
+                body += m.group(1)
+                blen = int(m.group(2))
+                if s:
+                    print("stream %d: %d DATA bytes added" % (s["id"], blen))
+                    padlen = 0
+                    if len(lines) > lidx + 2:
+                        mpad = re.match(r' +\(padlen=(\d+)\)', lines[lidx+2])
+                        if mpad: 
+                            padlen = int(mpad.group(1))
+                    s["paddings"].append(padlen)
+                    blen -= padlen
+                    s["response"]["body"] += body[-blen:].encode()
+                body = ''
+                skip_indents = True
+                continue
+                
+            m = re.match(r'\[.*] recv PUSH_PROMISE frame <.* stream_id=(\d+)>', l)
+            if m:
+                s = self.get_stream(streams, m.group(1))
+                if s:
+                    # headers we have are request headers for the PUSHed stream
+                    # these have been received on the originating stream, the promised
+                    # stream id it mentioned in the following lines
+                    print("stream %d: %d PUSH_PROMISE header" % (s["id"], len(s["header"])))
+                    if len(lines) > lidx+2:
+                        m2 = re.match(r'\s+\(.*promised_stream_id=(\d+)\)', lines[lidx+2])
+                        if m2:
+                            s2 = self.get_stream(streams, m2.group(1))
+                            s2["request"]["header"] = s["header"]
+                            s["promises"].append(s2)
+                    s["header"] = {} 
+                continue
+                    
+            m = re.match(r'(.*)\[.*] recv (\S+) frame <length=(\d+), .*stream_id=(\d+)>', l)
+            if m:
+                print("recv frame %s on stream %s" % (m.group(2), m.group(4)))
+                body += m.group(1)
+                skip_indents = True
+                continue
+                
+            m = re.match(r'(.*)\[.*] send (\S+) frame <length=(\d+), .*stream_id=(\d+)>', l)
+            if m:
+                print("send frame %s on stream %s" % (m.group(2), m.group(4)))
+                body += m.group(1)
+                skip_indents = True
+                continue
+                
+            if skip_indents and l.startswith('      '):
+                continue
+            
+            if '[' != l[0]:
+                skip_indents = None
+                body += l + '\n' 
+                
+        # the main request is done on the lowest odd numbered id
+        main_stream = 99999999999
+        for sid in streams:
+            s = streams[sid]
+            if "header" in s["response"] and ":status" in s["response"]["header"]:
+                s["response"]["status"] = int(s["response"]["header"][":status"])
+            if (sid % 2) == 1 and sid < main_stream:
+                main_stream = sid
+        
+        output["streams"] = streams
+        if main_stream in streams:
+            output["response"] = streams[main_stream]["response"]
+            output["paddings"] = streams[main_stream]["paddings"]
+        return output
+    
+    def _raw(self, url, timeout, options):
+        args = ["-v"]
+        if options:
+            args.extend(options)
+        r = self._baserun(url, timeout, args)
+        if 0 == r.exit_code:
+            r.add_results(self.parse_output(r.outraw))
+        return r
+
+    def get(self, url, timeout=5, options=None):
+        return self._raw(url, timeout, options)
+
+    def assets(self, url, timeout=5, options=None):
+        if not options:
+            options = []
+        options.extend(["-ans"])
+        r = self._baserun(url, timeout, options)
+        assets = []
+        if 0 == r.exit_code:
+            lines = re.findall(r'[^\n]*\n', r.stdout, re.MULTILINE)
+            for lidx, l in enumerate(lines):
+                m = re.match(r'\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+/(.*)', l)
+                if m:
+                    assets.append({
+                        "path": m.group(7),
+                        "status": int(m.group(5)),
+                        "size": m.group(6)
+                    })
+        assets.sort(key=_get_path)
+        r.add_assets(assets)
+        return r
+
+    def post_data(self, url, data, timeout=5, options=None):
+        reqbody = ("%s/nghttp.req.body" % self.TMP_DIR)
+        with open(reqbody, 'wb') as f:
+            f.write(data.encode('utf-8'))
+        if not options:
+            options = []
+        options.extend(["--data=%s" % reqbody])
+        return self._raw(url, timeout, options)
+
+    def post_name(self, url, name, timeout=5, options=None):
+        reqbody = ("%s/nghttp.req.body" % self.TMP_DIR)
+        with open(reqbody, 'w') as f:
+            f.write("--DSAJKcd9876\n")
+            f.write("Content-Disposition: form-data; name=\"value\"; filename=\"xxxxx\"\n")
+            f.write("Content-Type: text/plain\n")
+            f.write("\n%s\n" % name)
+            f.write("--DSAJKcd9876\n")
+        if not options:
+            options = []
+        options.extend(["--data=%s" % reqbody])
+        return self._raw(url, timeout, options)
+
+    def upload(self, url, fpath, timeout=5, options=None):
+        if not options:
+            options = []
+        options.extend(["--data=%s" % fpath])
+        return self._raw(url, timeout, options)
+
+    def upload_file(self, url, fpath, timeout=5, options=None):
+        fname = os.path.basename(fpath)
+        reqbody = ("%s/nghttp.req.body" % self.TMP_DIR)
+        with open(fpath, 'rb') as fin:
+            with open(reqbody, 'wb') as f:
+                f.write(("""--DSAJKcd9876
+Content-Disposition: form-data; name="xxx"; filename="xxxxx"
+Content-Type: text/plain
+
+testing mod_h2
+--DSAJKcd9876
+Content-Disposition: form-data; name="file"; filename="%s"
+Content-Type: application/octet-stream
+Content-Transfer-Encoding: binary
+
+""" % fname).encode('utf-8'))
+                f.write(fin.read())
+                f.write("""
+--DSAJKcd9876""".encode('utf-8'))
+        if not options:
+            options = []
+        options.extend([ 
+            "--data=%s" % reqbody, 
+            "--expect-continue", 
+            "-HContent-Type: multipart/form-data; boundary=DSAJKcd9876"])
+        return self._raw(url, timeout, options)
+
+    def _run(self, args) -> ExecResult:
+        print(("execute: %s" % " ".join(args)))
+        start = datetime.now()
+        p = subprocess.run(args, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+        return ExecResult(args=args, exit_code=p.returncode,
+                          stdout=p.stdout, stderr=p.stderr,
+                          duration=datetime.now() - start)

Added: httpd/httpd/branches/2.4.x/test/pyhttpd/result.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/pyhttpd/result.py?rev=1895945&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/pyhttpd/result.py (added)
+++ httpd/httpd/branches/2.4.x/test/pyhttpd/result.py Tue Dec 14 11:26:52 2021
@@ -0,0 +1,80 @@
+import json
+from datetime import timedelta
+from typing import Optional, Dict, List
+
+
+class ExecResult:
+
+    def __init__(self, args: List[str], exit_code: int,
+                 stdout: bytes, stderr: bytes = None, duration: timedelta = None):
+        self._args = args
+        self._exit_code = exit_code
+        self._raw = stdout if stdout else b''
+        self._stdout = stdout.decode() if stdout is not None else ""
+        self._stderr = stderr.decode() if stderr is not None else ""
+        self._duration = duration if duration is not None else timedelta()
+        self._response = None
+        self._results = {}
+        self._assets = []
+        # noinspection PyBroadException
+        try:
+            self._json_out = json.loads(self._stdout)
+        except:
+            self._json_out = None
+
+    def __repr__(self):
+        return f"ExecResult[code={self.exit_code}, args={self._args}, stdout={self.stdout}, stderr={self.stderr}]"
+
+    @property
+    def exit_code(self) -> int:
+        return self._exit_code
+
+    @property
+    def args(self) -> List[str]:
+        return self._args
+
+    @property
+    def outraw(self) -> bytes:
+        return self._raw
+
+    @property
+    def stdout(self) -> str:
+        return self._stdout
+
+    @property
+    def json(self) -> Optional[Dict]:
+        """Output as JSON dictionary or None if not parseable."""
+        return self._json_out
+
+    @property
+    def stderr(self) -> str:
+        return self._stderr
+
+    @property
+    def duration(self) -> timedelta:
+        return self._duration
+
+    @property
+    def response(self) -> Optional[Dict]:
+        return self._response
+
+    @property
+    def results(self) -> Dict:
+        return self._results
+
+    @property
+    def assets(self) -> List:
+        return self._assets
+
+    def add_response(self, resp: Dict):
+        if self._response:
+            resp['previous'] = self._response
+        self._response = resp
+
+    def add_results(self, results: Dict):
+        self._results.update(results)
+        if 'response' in results:
+            self.add_response(results['response'])
+
+    def add_assets(self, assets: List):
+        self._assets.extend(assets)