1+ namespace SqlStreamStore . HAL . Tests
2+ {
3+ using System ;
4+ using System . Collections . Generic ;
5+ using System . Linq ;
6+ using System . Net ;
7+ using System . Net . Http ;
8+ using System . Threading ;
9+ using System . Threading . Tasks ;
10+
11+ /// <summary>
12+ /// A delegating handler that handles HTTP redirects (301, 302, 303, 307, and 308).
13+ /// https://gist.github.com/joelverhagen/3be85bc0d5733756befa#file-redirectinghandler-cs
14+ /// </summary>
15+ internal class RedirectingHandler : DelegatingHandler
16+ {
17+ /// <summary>
18+ /// The property key used to access the list of responses.
19+ /// </summary>
20+ public const string HistoryPropertyKey = "Knapcode.Http.Handlers.RedirectingHandler.ResponseHistory" ;
21+
22+ private static readonly ISet < HttpStatusCode > s_redirectStatusCodes = new HashSet < HttpStatusCode > ( new [ ]
23+ {
24+ HttpStatusCode . MovedPermanently ,
25+ HttpStatusCode . Found ,
26+ HttpStatusCode . SeeOther ,
27+ HttpStatusCode . PermanentRedirect ,
28+ } ) ;
29+
30+ private static readonly ISet < HttpStatusCode > s_keepRequestBodyRedirectStatusCodes = new HashSet < HttpStatusCode > (
31+ new [ ]
32+ {
33+ HttpStatusCode . TemporaryRedirect ,
34+ HttpStatusCode . PermanentRedirect ,
35+ } ) ;
36+
37+
38+ /// <summary>
39+ /// Initializes a new instance of the <see cref="RedirectingHandler"/> class.
40+ /// </summary>
41+ public RedirectingHandler ( )
42+ {
43+ AllowAutoRedirect = true ;
44+ MaxAutomaticRedirections = 50 ;
45+ DisableInnerAutoRedirect = true ;
46+ DownloadContentOnRedirect = true ;
47+ KeepResponseHistory = true ;
48+ }
49+
50+ /// <summary>
51+ /// Gets or sets a value that indicates whether the handler should follow redirection responses.
52+ /// </summary>
53+ public bool AllowAutoRedirect { get ; set ; }
54+
55+ /// <summary>
56+ /// Gets or sets the maximum number of redirects that the handler follows.
57+ /// </summary>
58+ public int MaxAutomaticRedirections { get ; set ; }
59+
60+ /// <summary>
61+ /// Gets or sets a value indicating whether the response body should be downloaded before each redirection.
62+ /// </summary>
63+ public bool DownloadContentOnRedirect { get ; set ; }
64+
65+ /// <summary>
66+ /// Gets or sets a value indicating inner redirections on <see cref="HttpClientHandler"/> and <see cref="RedirectingHandler"/> should be disabled.
67+ /// </summary>
68+ public bool DisableInnerAutoRedirect { get ; set ; }
69+
70+ /// <summary>
71+ /// Gets or sets a value indicating whether the response history should be saved to the <see cref="HttpResponseMessage.RequestMessage"/> properties with the key of <see cref="HistoryPropertyKey"/>.
72+ /// </summary>
73+ public bool KeepResponseHistory { get ; set ; }
74+
75+ protected override async Task < HttpResponseMessage > SendAsync (
76+ HttpRequestMessage request ,
77+ CancellationToken cancellationToken )
78+ {
79+ if ( DisableInnerAutoRedirect )
80+ {
81+ // find the inner-most handler
82+ HttpMessageHandler innerHandler = InnerHandler ;
83+ while ( innerHandler is DelegatingHandler )
84+ {
85+ if ( innerHandler is RedirectingHandler redirectingHandler )
86+ {
87+ redirectingHandler . AllowAutoRedirect = false ;
88+ }
89+
90+ innerHandler = ( ( DelegatingHandler ) innerHandler ) . InnerHandler ;
91+ }
92+
93+ if ( innerHandler is HttpClientHandler httpClientHandler )
94+ {
95+ httpClientHandler . AllowAutoRedirect = false ;
96+ }
97+ }
98+
99+ // buffer the request body, to allow re-use in redirects
100+ HttpContent requestBody = null ;
101+ if ( AllowAutoRedirect && request . Content != null )
102+ {
103+ var buffer = await request . Content . ReadAsByteArrayAsync ( ) ;
104+ requestBody = new ByteArrayContent ( buffer ) ;
105+ foreach ( var header in request . Content . Headers )
106+ {
107+ requestBody . Headers . Add ( header . Key , header . Value ) ;
108+ }
109+ }
110+
111+ // make a copy of the request headers
112+ var requestHeaders = request
113+ . Headers
114+ . Select ( p => new KeyValuePair < string , string [ ] > ( p . Key , p . Value . ToArray ( ) ) )
115+ . ToArray ( ) ;
116+
117+ // send the initial request
118+ var response = await base . SendAsync ( request , cancellationToken ) ;
119+ var responses = new List < HttpResponseMessage > ( ) ;
120+
121+ var redirectCount = 0 ;
122+ while ( AllowAutoRedirect && redirectCount < MaxAutomaticRedirections
123+ && TryGetRedirectLocation ( response , out var locationString ) )
124+ {
125+ if ( DownloadContentOnRedirect && response . Content != null )
126+ {
127+ await response . Content . ReadAsByteArrayAsync ( ) ;
128+ }
129+
130+ Uri previousRequestUri = response . RequestMessage . RequestUri ;
131+
132+ // Credit where credit is due: https://github.com/kennethreitz/requests/blob/master/requests/sessions.py
133+ // allow redirection without a scheme
134+ if ( locationString . StartsWith ( "//" ) )
135+ {
136+ locationString = previousRequestUri . Scheme + ":" + locationString ;
137+ }
138+
139+ var nextRequestUri = new Uri ( locationString , UriKind . RelativeOrAbsolute ) ;
140+
141+ // allow relative redirects
142+ if ( ! nextRequestUri . IsAbsoluteUri )
143+ {
144+ nextRequestUri = new Uri ( previousRequestUri , nextRequestUri ) ;
145+ }
146+
147+ // override previous method
148+ HttpMethod nextMethod = response . RequestMessage . Method ;
149+ if ( response . StatusCode == HttpStatusCode . Moved && nextMethod == HttpMethod . Post
150+ || response . StatusCode == HttpStatusCode . Found && nextMethod != HttpMethod . Head
151+ || response . StatusCode == HttpStatusCode . SeeOther && nextMethod != HttpMethod . Head )
152+ {
153+ nextMethod = HttpMethod . Get ;
154+ requestBody = null ;
155+ }
156+
157+ if ( ! s_keepRequestBodyRedirectStatusCodes . Contains ( response . StatusCode ) )
158+ {
159+ requestBody = null ;
160+ }
161+
162+ // build the next request
163+ var nextRequest = new HttpRequestMessage ( nextMethod , nextRequestUri )
164+ {
165+ Content = requestBody ,
166+ Version = request . Version
167+ } ;
168+
169+ foreach ( var header in requestHeaders )
170+ {
171+ nextRequest . Headers . Add ( header . Key , header . Value ) ;
172+ }
173+
174+ foreach ( var pair in request . Properties )
175+ {
176+ nextRequest . Properties . Add ( pair . Key , pair . Value ) ;
177+ }
178+
179+ // keep a history all responses
180+ if ( KeepResponseHistory )
181+ {
182+ responses . Add ( response ) ;
183+ }
184+
185+ // send the next request
186+ response = await base . SendAsync ( nextRequest , cancellationToken ) ;
187+
188+ request = response . RequestMessage ;
189+ redirectCount ++ ;
190+ }
191+
192+ // save the history to the request message properties
193+ if ( KeepResponseHistory && response . RequestMessage != null )
194+ {
195+ responses . Add ( response ) ;
196+ response . RequestMessage . Properties . Add ( HistoryPropertyKey , responses ) ;
197+ }
198+
199+ return response ;
200+ }
201+
202+ private static bool TryGetRedirectLocation ( HttpResponseMessage response , out string location )
203+ {
204+ if ( s_redirectStatusCodes . Contains ( response . StatusCode )
205+ && response . Headers . TryGetValues ( "Location" , out var locations )
206+ && ( locations = locations . ToArray ( ) ) . Count ( ) == 1
207+ && ! string . IsNullOrWhiteSpace ( locations . First ( ) ) )
208+ {
209+ location = locations . First ( ) . Trim ( ) ;
210+ return true ;
211+ }
212+
213+ location = null ;
214+ return false ;
215+ }
216+ }
217+ }
0 commit comments