@@ -539,6 +539,31 @@ public override void RemoveAt(int index)
539539 {
540540 s_instance . RaiseResponseEvent ( request , response ) ;
541541 }
542+ else
543+ {
544+ // In case reponse content length is 0 and request is async,
545+ // we won't have a HttpWebResponse set on request object when this method is called
546+ // http://referencesource.microsoft.com/#System/net/System/Net/HttpWebResponse.cs,525
547+
548+ // But we there will be CoreResponseData object that is either exception
549+ // or the internal HTTP reponse representation having status, content and headers
550+
551+ var coreResponse = s_coreResponseAccessor ( request ) ;
552+ if ( coreResponse != null && s_coreResponseDataType . IsInstanceOfType ( coreResponse ) )
553+ {
554+ HttpStatusCode status = s_coreStatusCodeAccessor ( coreResponse ) ;
555+ WebHeaderCollection headers = s_coreHeadersAccessor ( coreResponse ) ;
556+
557+ // Manual creation of HttpWebResponse here is not possible as this method is eventually called from the
558+ // HttpWebResponse ctor. So we will send Stop event with the Status and Headers payload
559+ // to notify listeners about response;
560+ // We use two different names for Stop events since one event with payload type that varies creates
561+ // complications for efficient payload parsing and is not supported by DiagnosicSource helper
562+ // libraries (e.g. Microsoft.Extensions.DiagnosticAdapter)
563+
564+ s_instance . RaiseResponseEvent ( request , status , headers ) ;
565+ }
566+ }
542567 }
543568
544569 base . RemoveAt ( index ) ;
@@ -606,22 +631,33 @@ private void RaiseResponseEvent(HttpWebRequest request, HttpWebResponse response
606631 // Response event could be received several times for the same request in case it was redirected
607632 // IsLastResponse checks if response is the last one (no more redirects will happen)
608633 // based on response StatusCode and number or redirects done so far
609- if ( request . Headers [ RequestIdHeaderName ] != null && IsLastResponse ( request , response ) )
634+ if ( request . Headers [ RequestIdHeaderName ] != null && IsLastResponse ( request , response . StatusCode ) )
610635 {
611636 // only send Stop if request was instrumented
612637 this . Write ( RequestStopName , new { Request = request , Response = response } ) ;
613638 }
614639 }
615640
616- private bool IsLastResponse ( HttpWebRequest request , HttpWebResponse response )
641+ private void RaiseResponseEvent ( HttpWebRequest request , HttpStatusCode statusCode , WebHeaderCollection headers )
642+ {
643+ // Response event could be received several times for the same request in case it was redirected
644+ // IsLastResponse checks if response is the last one (no more redirects will happen)
645+ // based on response StatusCode and number or redirects done so far
646+ if ( request . Headers [ RequestIdHeaderName ] != null && IsLastResponse ( request , statusCode ) )
647+ {
648+ this . Write ( RequestStopExName , new { Request = request , StatusCode = statusCode , Headers = headers } ) ;
649+ }
650+ }
651+
652+ private bool IsLastResponse ( HttpWebRequest request , HttpStatusCode statusCode )
617653 {
618654 if ( request . AllowAutoRedirect )
619655 {
620- if ( response . StatusCode == HttpStatusCode . Ambiguous || // 300
621- response . StatusCode == HttpStatusCode . Moved || // 301
622- response . StatusCode == HttpStatusCode . Redirect || // 302
623- response . StatusCode == HttpStatusCode . RedirectMethod || // 303
624- response . StatusCode == HttpStatusCode . RedirectKeepVerb ) // 307
656+ if ( statusCode == HttpStatusCode . Ambiguous || // 300
657+ statusCode == HttpStatusCode . Moved || // 301
658+ statusCode == HttpStatusCode . Redirect || // 302
659+ statusCode == HttpStatusCode . RedirectMethod || // 303
660+ statusCode == HttpStatusCode . RedirectKeepVerb ) // 307
625661 {
626662 return s_autoRedirectsAccessor ( request ) >= request . MaximumAutomaticRedirections ;
627663 }
@@ -642,40 +678,27 @@ private static void PrepareReflectionObjects()
642678 s_connectionType = systemNetHttpAssembly ? . GetType ( "System.Net.Connection" ) ;
643679 s_writeListField = s_connectionType ? . GetField ( "m_WriteList" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
644680
645- // Second step: Generate an accessor for HttpWebRequest._HttpResponse
646- FieldInfo responseField = typeof ( HttpWebRequest ) . GetField ( "_HttpResponse" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
647- if ( responseField != null )
648- {
649- string methodName = responseField . ReflectedType . FullName + ".get_" + responseField . Name ;
650- DynamicMethod getterMethod = new DynamicMethod ( methodName , typeof ( HttpWebResponse ) , new Type [ ] { typeof ( HttpWebRequest ) } , true ) ;
651- ILGenerator generator = getterMethod . GetILGenerator ( ) ;
652- generator . Emit ( OpCodes . Ldarg_0 ) ;
653- generator . Emit ( OpCodes . Ldfld , responseField ) ;
654- generator . Emit ( OpCodes . Ret ) ;
655- s_httpResponseAccessor = ( Func < HttpWebRequest , HttpWebResponse > ) getterMethod . CreateDelegate ( typeof ( Func < HttpWebRequest , HttpWebResponse > ) ) ;
656- }
681+ s_httpResponseAccessor = CreateFieldGetter < HttpWebRequest , HttpWebResponse > ( "_HttpResponse" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
682+ s_autoRedirectsAccessor = CreateFieldGetter < HttpWebRequest , int > ( "_AutoRedirects" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
683+ s_coreResponseAccessor = CreateFieldGetter < HttpWebRequest , object > ( "_CoreResponse" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
657684
658- // Third step: Generate an accessor for HttpWebRequest._AutoRedirects
659- FieldInfo redirectsField = typeof ( HttpWebRequest ) . GetField ( "_AutoRedirects" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
660- if ( redirectsField != null )
685+ s_coreResponseDataType = systemNetHttpAssembly ? . GetType ( "System.Net.CoreResponseData" ) ;
686+ if ( s_coreResponseDataType != null )
661687 {
662- string methodName = redirectsField . ReflectedType . FullName + ".get_" + redirectsField . Name ;
663- DynamicMethod getterMethod = new DynamicMethod ( methodName , typeof ( int ) , new Type [ ] { typeof ( HttpWebRequest ) } , true ) ;
664- ILGenerator generator = getterMethod . GetILGenerator ( ) ;
665- generator . Emit ( OpCodes . Ldarg_0 ) ;
666- generator . Emit ( OpCodes . Ldfld , redirectsField ) ;
667- generator . Emit ( OpCodes . Ret ) ;
668- s_autoRedirectsAccessor = ( Func < HttpWebRequest , int > ) getterMethod . CreateDelegate ( typeof ( Func < HttpWebRequest , int > ) ) ;
688+ s_coreStatusCodeAccessor = CreateFieldGetter < HttpStatusCode > ( s_coreResponseDataType , "m_StatusCode" , BindingFlags . Public | BindingFlags . Instance ) ;
689+ s_coreHeadersAccessor = CreateFieldGetter < WebHeaderCollection > ( s_coreResponseDataType , "m_ResponseHeaders" , BindingFlags . Public | BindingFlags . Instance ) ;
669690 }
670-
671691 // Double checking to make sure we have all the pieces initialized
672692 if ( s_connectionGroupListField == null ||
673693 s_connectionGroupType == null ||
674694 s_connectionListField == null ||
675695 s_connectionType == null ||
676696 s_writeListField == null ||
677697 s_httpResponseAccessor == null ||
678- s_autoRedirectsAccessor == null )
698+ s_autoRedirectsAccessor == null ||
699+ s_coreResponseDataType == null ||
700+ s_coreStatusCodeAccessor == null ||
701+ s_coreHeadersAccessor == null )
679702 {
680703 // If anything went wrong here, just return false. There is nothing we can do.
681704 throw new InvalidOperationException ( "Unable to initialize all required reflection objects" ) ;
@@ -697,7 +720,48 @@ private static void PerformInjection()
697720 servicePointTableField . SetValue ( null , newTable ) ;
698721 }
699722
700- #endregion
723+ private static Func < TClass , TField > CreateFieldGetter < TClass , TField > ( string fieldName , BindingFlags flags ) where TClass : class
724+ {
725+ FieldInfo field = typeof ( TClass ) . GetField ( fieldName , flags ) ;
726+ if ( field != null )
727+ {
728+ string methodName = field . ReflectedType . FullName + ".get_" + field . Name ;
729+ DynamicMethod getterMethod = new DynamicMethod ( methodName , typeof ( TField ) , new [ ] { typeof ( TClass ) } , true ) ;
730+ ILGenerator generator = getterMethod . GetILGenerator ( ) ;
731+ generator . Emit ( OpCodes . Ldarg_0 ) ;
732+ generator . Emit ( OpCodes . Ldfld , field ) ;
733+ generator . Emit ( OpCodes . Ret ) ;
734+ return ( Func < TClass , TField > ) getterMethod . CreateDelegate ( typeof ( Func < TClass , TField > ) ) ;
735+ }
736+
737+ return null ;
738+ }
739+
740+
741+ /// <summary>
742+ /// Creates getter for a field defined in private or internal type
743+ /// repesented with classType variable
744+ /// </summary>
745+ private static Func < object , TField > CreateFieldGetter < TField > ( Type classType , string fieldName , BindingFlags flags )
746+ {
747+ FieldInfo field = classType . GetField ( fieldName , flags ) ;
748+ if ( field != null )
749+ {
750+ string methodName = classType . FullName + ".get_" + field . Name ;
751+ DynamicMethod getterMethod = new DynamicMethod ( methodName , typeof ( TField ) , new [ ] { typeof ( object ) } , true ) ;
752+ ILGenerator generator = getterMethod . GetILGenerator ( ) ;
753+ generator . Emit ( OpCodes . Ldarg_0 ) ;
754+ generator . Emit ( OpCodes . Castclass , classType ) ;
755+ generator . Emit ( OpCodes . Ldfld , field ) ;
756+ generator . Emit ( OpCodes . Ret ) ;
757+
758+ return ( Func < object , TField > ) getterMethod . CreateDelegate ( typeof ( Func < object , TField > ) ) ;
759+ }
760+
761+ return null ;
762+ }
763+
764+ #endregion
701765
702766 internal static HttpHandlerDiagnosticListener s_instance = new HttpHandlerDiagnosticListener ( ) ;
703767
@@ -706,6 +770,7 @@ private static void PerformInjection()
706770 private const string ActivityName = "System.Net.Http.Desktop.HttpRequestOut" ;
707771 private const string RequestStartName = "System.Net.Http.Desktop.HttpRequestOut.Start" ;
708772 private const string RequestStopName = "System.Net.Http.Desktop.HttpRequestOut.Stop" ;
773+ private const string RequestStopExName = "System.Net.Http.Desktop.HttpRequestOut.Ex.Stop" ;
709774 private const string InitializationFailed = "System.Net.Http.InitializationFailed" ;
710775 private const string RequestIdHeaderName = "Request-Id" ;
711776 private const string CorrelationContextHeaderName = "Correlation-Context" ;
@@ -721,7 +786,11 @@ private static void PerformInjection()
721786 private static FieldInfo s_writeListField ;
722787 private static Func < HttpWebRequest , HttpWebResponse > s_httpResponseAccessor ;
723788 private static Func < HttpWebRequest , int > s_autoRedirectsAccessor ;
789+ private static Func < HttpWebRequest , object > s_coreResponseAccessor ;
790+ private static Func < object , HttpStatusCode > s_coreStatusCodeAccessor ;
791+ private static Func < object , WebHeaderCollection > s_coreHeadersAccessor ;
792+ private static Type s_coreResponseDataType ;
724793
725- #endregion
794+ #endregion
726795 }
727796}
0 commit comments