@@ -122,34 +122,9 @@ await sslStream.AuthenticateAsServerAsync(
122122
123123 public async Task AcceptConnectionAsync ( Func < Connection , Task > funcAsync )
124124 {
125- using ( Socket s = await _listenSocket . AcceptAsync ( ) . ConfigureAwait ( false ) )
125+ using ( Connection connection = await EstablishConnectionAsync ( ) . ConfigureAwait ( false ) )
126126 {
127- s . NoDelay = true ;
128-
129- Stream stream = new NetworkStream ( s , ownsSocket : false ) ;
130- if ( _options . UseSsl )
131- {
132- var sslStream = new SslStream ( stream , false , delegate { return true ; } ) ;
133- using ( var cert = Configuration . Certificates . GetServerCertificate ( ) )
134- {
135- await sslStream . AuthenticateAsServerAsync (
136- cert ,
137- clientCertificateRequired : true , // allowed but not required
138- enabledSslProtocols : _options . SslProtocols ,
139- checkCertificateRevocation : false ) . ConfigureAwait ( false ) ;
140- }
141- stream = sslStream ;
142- }
143-
144- if ( _options . StreamWrapper != null )
145- {
146- stream = _options . StreamWrapper ( stream ) ;
147- }
148-
149- using ( var connection = new Connection ( s , stream ) )
150- {
151- await funcAsync ( connection ) . ConfigureAwait ( false ) ;
152- }
127+ await funcAsync ( connection ) . ConfigureAwait ( false ) ;
153128 }
154129 }
155130
@@ -400,9 +375,8 @@ public static string GetConnectionCloseResponse(HttpStatusCode statusCode = Http
400375 "\r \n " +
401376 content ;
402377
403- public class Options
378+ public class Options : GenericLoopbackOptions
404379 {
405- public IPAddress Address { get ; set ; } = IPAddress . Loopback ;
406380 public int ListenBacklog { get ; set ; } = 1 ;
407381 public bool UseSsl { get ; set ; } = false ;
408382 public SslProtocols SslProtocols { get ; set ; } =
@@ -761,36 +735,53 @@ public override async Task<Byte[]> ReadRequestBodyAsync()
761735 return buffer ;
762736 }
763737
764- public override async Task SendResponseAsync ( HttpStatusCode statusCode = HttpStatusCode . OK , IList < HttpHeaderData > headers = null , string content = null , bool isFinal = true , int requestId = 0 )
738+ public override async Task SendResponseAsync ( HttpStatusCode ? statusCode = HttpStatusCode . OK , IList < HttpHeaderData > headers = null , string content = null , bool isFinal = true , int requestId = 0 )
765739 {
766740 string headerString = null ;
767741 int contentLength = - 1 ;
742+ bool isChunked = false ;
743+ bool hasContentLength = false ;
768744
769745 if ( headers != null )
770746 {
747+ // Process given headers and look for some well-known cases.
771748 foreach ( HttpHeaderData headerData in headers )
772749 {
773- headerString = headerString + $ "{ headerData . Name } : { headerData . Value } \r \n ";
774750 if ( headerData . Name . Equals ( "Content-Length" , StringComparison . OrdinalIgnoreCase ) )
775751 {
752+ hasContentLength = true ;
753+ if ( headerData . Value == null )
754+ {
755+ continue ;
756+ }
757+
776758 contentLength = int . Parse ( headerData . Value ) ;
777759 }
760+ else if ( headerData . Name . Equals ( "Transfer-Encoding" , StringComparison . OrdinalIgnoreCase ) && headerData . Value . Equals ( "chunked" , StringComparison . OrdinalIgnoreCase ) )
761+ {
762+ isChunked = true ;
763+ }
764+
765+ headerString = headerString + $ "{ headerData . Name } : { headerData . Value } \r \n ";
778766 }
779767 }
780768
781- if ( contentLength < 0 )
769+ bool endHeaders = content != null || isFinal ;
770+ if ( statusCode != null )
782771 {
783- // We did not find Content header in headers.
784- contentLength = String . IsNullOrEmpty ( content ) ? 0 : content . Length ;
772+ // If we need to send status line, prepped it to headers and possibly add missing headers to the end.
773+ headerString =
774+ $ "HTTP/1.1 { ( int ) statusCode } { GetStatusDescription ( ( HttpStatusCode ) statusCode ) } \r \n " +
775+ ( ! hasContentLength && ! isChunked && content != null ? $ "Content-length: { content . Length } \r \n " : "" ) +
776+ headerString +
777+ ( endHeaders ? "\r \n " : "" ) ;
785778 }
786779
787- if ( content != null || isFinal )
780+ await SendResponseAsync ( headerString ) . ConfigureAwait ( false ) ;
781+ if ( content != null )
788782 {
789- headerString = GetHttpResponseHeaders ( statusCode , headerString , contentLength , connectionClose : true ) ;
783+ await SendResponseBodyAsync ( content , isFinal : isFinal , requestId : requestId ) . ConfigureAwait ( false ) ;
790784 }
791-
792- await SendResponseAsync ( headerString ) . ConfigureAwait ( false ) ;
793- await SendResponseAsync ( content ) . ConfigureAwait ( false ) ;
794785 }
795786
796787 public override async Task SendResponseHeadersAsync ( HttpStatusCode statusCode = HttpStatusCode . OK , IList < HttpHeaderData > headers = null , int requestId = 0 )
@@ -835,13 +826,34 @@ public override async Task WaitForCancellationAsync(bool ignoreIncomingData = tr
835826 }
836827 }
837828
838- public override async Task < HttpRequestData > HandleRequestAsync ( HttpStatusCode statusCode = HttpStatusCode . OK , IList < HttpHeaderData > headers = null , string content = null )
829+ public override async Task < HttpRequestData > HandleRequestAsync ( HttpStatusCode statusCode = HttpStatusCode . OK , IList < HttpHeaderData > headers = null , string content = "" )
839830 {
840831 using ( Connection connection = await EstablishConnectionAsync ( ) . ConfigureAwait ( false ) )
841832 {
842833 HttpRequestData requestData = await connection . ReadRequestDataAsync ( ) . ConfigureAwait ( false ) ;
843- await connection . SendResponseAsync ( statusCode , headers , content : content ) . ConfigureAwait ( false ) ;
844834
835+ // For historical reasons, we added Date and "Connection: close" (to improve test reliability)
836+ bool hasDate = false ;
837+ List < HttpHeaderData > newHeaders = new List < HttpHeaderData > ( ) ;
838+ if ( headers != null )
839+ {
840+ foreach ( var header in headers )
841+ {
842+ newHeaders . Add ( header ) ;
843+ if ( header . Name . Equals ( "Date" , StringComparison . OrdinalIgnoreCase ) )
844+ {
845+ hasDate = true ;
846+ }
847+ }
848+ }
849+
850+ newHeaders . Add ( new HttpHeaderData ( "Connection" , "Close" ) ) ;
851+ if ( ! hasDate )
852+ {
853+ newHeaders . Add ( new HttpHeaderData ( "Date" , "{DateTimeOffset.UtcNow:R}" ) ) ;
854+ }
855+
856+ await connection . SendResponseAsync ( statusCode , newHeaders , content : content ) . ConfigureAwait ( false ) ;
845857 return requestData ;
846858 }
847859 }
@@ -856,9 +868,15 @@ public sealed class Http11LoopbackServerFactory : LoopbackServerFactory
856868 {
857869 public static readonly Http11LoopbackServerFactory Singleton = new Http11LoopbackServerFactory ( ) ;
858870
859- public override Task CreateServerAsync ( Func < GenericLoopbackServer , Uri , Task > funcAsync , int millisecondsTimeout = 60_000 )
871+ public override Task CreateServerAsync ( Func < GenericLoopbackServer , Uri , Task > funcAsync , int millisecondsTimeout = 60_000 , GenericLoopbackOptions options = null )
860872 {
861- return LoopbackServer . CreateServerAsync ( ( server , uri ) => funcAsync ( server , uri ) ) ;
873+ LoopbackServer . Options newOptions = new LoopbackServer . Options ( ) ;
874+ if ( options != null )
875+ {
876+ newOptions . Address = options . Address ;
877+ }
878+
879+ return LoopbackServer . CreateServerAsync ( ( server , uri ) => funcAsync ( server , uri ) , options : newOptions ) ;
862880 }
863881
864882 public override bool IsHttp11 => true ;
0 commit comments