@@ -42,7 +42,7 @@ async def _read_from_peer(self):
4242 request_line_end = data .index (b'\r \n ' )
4343 request_line = data [:request_line_end ]
4444 rest = data [request_line_end :]
45-
45+
4646 try :
4747 # Try to decode and check if there are non-ASCII chars
4848 request_line_str = request_line .decode ('ascii' )
@@ -66,7 +66,7 @@ async def _read_from_peer(self):
6666 else :
6767 encoded_query_parts .append (param )
6868 target_str = path + '?' + '&' .join (encoded_query_parts )
69-
69+
7070 request_line = b' ' .join ([method , target_str .encode ('ascii' ), version ])
7171 data = request_line + rest
7272 except (ConnectionError , trio .BrokenResourceError , trio .ClosedResourceError ):
@@ -87,7 +87,7 @@ async def shutdown_and_clean_up(self):
8787 await self .stream .send_eof ()
8888 except (trio .BrokenResourceError , AttributeError ):
8989 pass
90-
90+
9191 with trio .move_on_after (TIMEOUT ):
9292 try :
9393 while True :
@@ -96,7 +96,7 @@ async def shutdown_and_clean_up(self):
9696 break
9797 except (trio .BrokenResourceError , trio .ClosedResourceError ):
9898 pass
99-
99+
100100 try :
101101 await self .stream .aclose ()
102102 except (trio .BrokenResourceError , trio .ClosedResourceError ):
@@ -177,36 +177,59 @@ async def maybe_send_error_response(wrapper, exc):
177177
178178async def send_echo_response (wrapper , request ):
179179 wrapper .info ("Preparing echo response" )
180-
180+
181181 body_data = b""
182182 while True :
183183 event = await wrapper .next_event ()
184184 if type (event ) is h11 .EndOfMessage :
185185 break
186186 assert type (event ) is h11 .Data
187187 body_data += event .data
188-
188+
189189 target = request .target if isinstance (request .target , bytes ) else request .target .encode ()
190190 target_str = target .decode ("utf-8" , errors = "replace" )
191-
191+
192+ # Check if this is the /no-content-length endpoint
193+ if target_str .startswith ("/no-content-length" ):
194+ wrapper .info ("Sending raw response without Content-Length or Transfer-Encoding" )
195+ response_body = b"Response body without Content-Length header"
196+
197+ # Send raw HTTP response to avoid h11 adding Transfer-Encoding: chunked
198+ # Build the raw HTTP response
199+ response_lines = [
200+ b"HTTP/1.1 200 OK" ,
201+ b"Server: echo-server" ,
202+ b"Content-Type: text/plain; charset=utf-8" ,
203+ b"Connection: close" ,
204+ b"" , # Empty line separates headers from body
205+ ]
206+ raw_response = b"\r \n " .join (response_lines ) + b"\r \n " + response_body
207+
208+ # Send raw response directly to the stream, bypassing h11
209+ await wrapper .stream .send_all (raw_response )
210+
211+ # Mark connection as must close since we sent raw data
212+ wrapper .conn .send_failed () # This marks the connection as broken in h11's view
213+ return
214+
192215 # Check if this is the /404 endpoint
193216 if target_str .startswith ("/404" ):
194217 status_code = 404
195218 else :
196219 status_code = 200
197-
220+
198221 response_json = {"data" : body_data .decode ("utf-8" )}
199222 response_body = json .dumps (response_json , indent = 4 ).encode ("utf-8" )
200-
223+
201224 headers = wrapper .basic_headers ()
202225 headers .append (("Content-Type" , "application/json; charset=utf-8" ))
203226 headers .append (("Content-Length" , str (len (response_body ))))
204-
227+
205228 for header_name , header_value in request .headers :
206229 echo_name = b"Echo-" + header_name if isinstance (header_name , bytes ) else f"Echo-{ header_name } " .encode ()
207230 echo_value = header_value if isinstance (header_value , bytes ) else str (header_value ).encode ()
208231 headers .append ((echo_name , echo_value ))
209-
232+
210233 res = h11 .Response (status_code = status_code , headers = headers )
211234 await wrapper .send (res )
212235 await wrapper .send (h11 .Data (data = response_body ))
@@ -225,20 +248,20 @@ async def serve_ssl(port, cert_file=os.path.join(os.path.dirname(__file__),
225248 "../resources/unittests.crt" ), key_file = os .path .join (os .path .dirname (__file__ ),
226249 "../resources/unittests.key" )):
227250 import ssl
228-
251+
229252 script_dir = os .path .dirname (os .path .abspath (__file__ ))
230253 cert_path = os .path .join (script_dir , cert_file )
231254 key_path = os .path .join (script_dir , key_file )
232-
255+
233256 if not os .path .exists (cert_path ) or not os .path .exists (key_path ):
234257 print (f"Warning: SSL certificates not found at { cert_path } and { key_path } " )
235258 print (f"Skipping HTTPS server on port { port } " )
236259 print (f"To enable HTTPS, run: openssl req -x509 -newkey rsa:2048 -keyout { key_file } -out { cert_file } -days 365 -nodes -subj '/C=US/ST=WA/L=Seattle/O=Test/CN=localhost'" )
237260 return
238-
261+
239262 ssl_context = ssl .SSLContext (ssl .PROTOCOL_TLS_SERVER )
240263 ssl_context .load_cert_chain (cert_path , key_path )
241-
264+
242265 print (f"listening on https://localhost:{ port } " )
243266 try :
244267 await trio .serve_ssl_over_tcp (http_serve , port , ssl_context )
@@ -249,7 +272,7 @@ async def serve_ssl(port, cert_file=os.path.join(os.path.dirname(__file__),
249272async def main ():
250273 http_port = os .environ .get ('HTTP_PORT' )
251274 https_port = os .environ .get ('HTTPS_PORT' )
252-
275+
253276 async with trio .open_nursery () as nursery :
254277 nursery .start_soon (serve , 8081 if not http_port else int (http_port ))
255278 nursery .start_soon (serve_ssl , 8082 if not https_port else int (https_port ))
0 commit comments