in GVFS/GVFS.Common/Http/HttpRequestor.cs [73:254]
protected GitEndPointResponseData SendRequest(
long requestId,
Uri requestUri,
HttpMethod httpMethod,
string requestContent,
CancellationToken cancellationToken,
MediaTypeWithQualityHeaderValue acceptType = null)
{
string authString = null;
string errorMessage;
if (!this.authentication.IsAnonymous &&
!this.authentication.TryGetCredentials(this.Tracer, out authString, out errorMessage))
{
return new GitEndPointResponseData(
HttpStatusCode.Unauthorized,
new GitObjectsHttpException(HttpStatusCode.Unauthorized, errorMessage),
shouldRetry: true,
message: null,
onResponseDisposed: null);
}
HttpRequestMessage request = new HttpRequestMessage(httpMethod, requestUri);
// By default, VSTS auth failures result in redirects to SPS to reauthenticate.
// To provide more consistent behavior when using the GCM, have them send us 401s instead
request.Headers.Add("X-TFS-FedAuthRedirect", "Suppress");
request.Headers.UserAgent.Add(this.userAgentHeader);
if (!this.authentication.IsAnonymous)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", authString);
}
if (acceptType != null)
{
request.Headers.Accept.Add(acceptType);
}
if (requestContent != null)
{
request.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
}
EventMetadata responseMetadata = new EventMetadata();
responseMetadata.Add("RequestId", requestId);
responseMetadata.Add("availableConnections", availableConnections.CurrentCount);
Stopwatch requestStopwatch = Stopwatch.StartNew();
availableConnections.Wait(cancellationToken);
TimeSpan connectionWaitTime = requestStopwatch.Elapsed;
TimeSpan responseWaitTime = default(TimeSpan);
GitEndPointResponseData gitEndPointResponseData = null;
HttpResponseMessage response = null;
try
{
requestStopwatch.Restart();
try
{
response = this.client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
}
finally
{
responseWaitTime = requestStopwatch.Elapsed;
}
responseMetadata.Add("CacheName", GetSingleHeaderOrEmpty(response.Headers, "X-Cache-Name"));
responseMetadata.Add("StatusCode", response.StatusCode);
if (response.StatusCode == HttpStatusCode.OK)
{
string contentType = GetSingleHeaderOrEmpty(response.Content.Headers, "Content-Type");
responseMetadata.Add("ContentType", contentType);
if (!this.authentication.IsAnonymous)
{
this.authentication.ApproveCredentials(this.Tracer, authString);
}
Stream responseStream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
gitEndPointResponseData = new GitEndPointResponseData(
response.StatusCode,
contentType,
responseStream,
message: response,
onResponseDisposed: () => availableConnections.Release());
}
else
{
errorMessage = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
int statusInt = (int)response.StatusCode;
bool shouldRetry = ShouldRetry(response.StatusCode);
if (response.StatusCode == HttpStatusCode.Unauthorized &&
this.authentication.IsAnonymous)
{
shouldRetry = false;
errorMessage = "Anonymous request was rejected with a 401";
}
else if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.BadRequest || response.StatusCode == HttpStatusCode.Redirect)
{
this.authentication.RejectCredentials(this.Tracer, authString);
if (!this.authentication.IsBackingOff)
{
errorMessage = string.Format("Server returned error code {0} ({1}). Your PAT may be expired and we are asking for a new one. Original error message from server: {2}", statusInt, response.StatusCode, errorMessage);
}
else
{
errorMessage = string.Format("Server returned error code {0} ({1}) after successfully renewing your PAT. You may not have access to this repo. Original error message from server: {2}", statusInt, response.StatusCode, errorMessage);
}
}
else
{
errorMessage = string.Format("Server returned error code {0} ({1}). Original error message from server: {2}", statusInt, response.StatusCode, errorMessage);
}
gitEndPointResponseData = new GitEndPointResponseData(
response.StatusCode,
new GitObjectsHttpException(response.StatusCode, errorMessage),
shouldRetry,
message: response,
onResponseDisposed: () => availableConnections.Release());
}
}
catch (TaskCanceledException)
{
cancellationToken.ThrowIfCancellationRequested();
errorMessage = string.Format("Request to {0} timed out", requestUri);
gitEndPointResponseData = new GitEndPointResponseData(
HttpStatusCode.RequestTimeout,
new GitObjectsHttpException(HttpStatusCode.RequestTimeout, errorMessage),
shouldRetry: true,
message: response,
onResponseDisposed: () => availableConnections.Release());
}
catch (HttpRequestException httpRequestException) when (httpRequestException.InnerException is System.Security.Authentication.AuthenticationException)
{
// This exception is thrown on OSX, when user declines to give permission to access certificate
gitEndPointResponseData = new GitEndPointResponseData(
HttpStatusCode.Unauthorized,
httpRequestException.InnerException,
shouldRetry: false,
message: response,
onResponseDisposed: () => availableConnections.Release());
}
catch (WebException ex)
{
gitEndPointResponseData = new GitEndPointResponseData(
HttpStatusCode.InternalServerError,
ex,
shouldRetry: true,
message: response,
onResponseDisposed: () => availableConnections.Release());
}
finally
{
responseMetadata.Add("connectionWaitTimeMS", $"{connectionWaitTime.TotalMilliseconds:F4}");
responseMetadata.Add("responseWaitTimeMS", $"{responseWaitTime.TotalMilliseconds:F4}");
this.Tracer.RelatedEvent(EventLevel.Informational, "NetworkResponse", responseMetadata);
if (gitEndPointResponseData == null)
{
// If gitEndPointResponseData is null there was an unhandled exception
if (response != null)
{
response.Dispose();
}
availableConnections.Release();
}
}
return gitEndPointResponseData;
}