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 (!string.IsNullOrEmpty(authString)) { 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); 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); }
public virtual List <GitObjectSize> QueryForFileSizes(IEnumerable <string> objectIds, CancellationToken cancellationToken) { long requestId = HttpRequestor.GetNewRequestId(); string objectIdsJson = ToJsonList(objectIds); Uri cacheServerEndpoint = new Uri(this.CacheServer.SizesEndpointUrl); Uri originEndpoint = new Uri(this.enlistment.RepoUrl + GVFSConstants.Endpoints.GVFSSizes); EventMetadata metadata = new EventMetadata(); metadata.Add("RequestId", requestId); int objectIdCount = objectIds.Count(); if (objectIdCount > 10) { metadata.Add("ObjectIdCount", objectIdCount); } else { metadata.Add("ObjectIdJson", objectIdsJson); } this.Tracer.RelatedEvent(EventLevel.Informational, "QueryFileSizes", metadata, Keywords.Network); RetryWrapper <List <GitObjectSize> > retrier = new RetryWrapper <List <GitObjectSize> >(this.RetryConfig.MaxAttempts, cancellationToken); retrier.OnFailure += RetryWrapper <List <GitObjectSize> > .StandardErrorHandler(this.Tracer, requestId, "QueryFileSizes"); RetryWrapper <List <GitObjectSize> > .InvocationResult requestTask = retrier.Invoke( tryCount => { Uri gvfsEndpoint; if (nextCacheServerAttemptTime < DateTime.Now) { gvfsEndpoint = cacheServerEndpoint; } else { gvfsEndpoint = originEndpoint; } using (GitEndPointResponseData response = this.SendRequest(requestId, gvfsEndpoint, HttpMethod.Post, objectIdsJson, cancellationToken)) { if (response.StatusCode == HttpStatusCode.NotFound) { nextCacheServerAttemptTime = nextCacheServerAttemptTime.AddMinutes(15); return(new RetryWrapper <List <GitObjectSize> > .CallbackResult(response.Error, true)); } if (response.HasErrors) { return(new RetryWrapper <List <GitObjectSize> > .CallbackResult(response.Error, response.ShouldRetry)); } string objectSizesString = response.RetryableReadToEnd(); List <GitObjectSize> objectSizes = JsonConvert.DeserializeObject <List <GitObjectSize> >(objectSizesString); return(new RetryWrapper <List <GitObjectSize> > .CallbackResult(objectSizes)); } }); return(requestTask.Result ?? new List <GitObjectSize>(0)); }
protected GitEndPointResponseData SendRequest( long requestId, Uri requestUri, HttpMethod httpMethod, string requestContent, CancellationToken cancellationToken, MediaTypeWithQualityHeaderValue acceptType = null) { string authString; string errorMessage; if (!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); request.Headers.UserAgent.Add(this.userAgentHeader); if (!string.IsNullOrEmpty(authString)) { 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); this.authentication.ConfirmCredentialsWorked(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; if (string.IsNullOrWhiteSpace(errorMessage)) { if (response.StatusCode == HttpStatusCode.Unauthorized) { this.authentication.Revoke(authString); if (!this.authentication.IsBackingOff) { errorMessage = "Server returned error code 401 (Unauthorized). Your PAT may be expired and we are asking for a new one."; } else { errorMessage = "Server returned error code 401 (Unauthorized) after successfully renewing your PAT. You may not have access to this repo"; } } else { errorMessage = string.Format("Server returned error code {0} ({1})", statusInt, response.StatusCode); } } gitEndPointResponseData = new GitEndPointResponseData( response.StatusCode, new GitObjectsHttpException(response.StatusCode, errorMessage), ShouldRetry(response.StatusCode), 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 (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); }
public bool TryQueryGVFSConfig(out GVFSConfig gvfsConfig) { gvfsConfig = null; Uri gvfsConfigEndpoint; string gvfsConfigEndpointString = this.repoUrl + GVFSConstants.Endpoints.GVFSConfig; try { gvfsConfigEndpoint = new Uri(gvfsConfigEndpointString); } catch (UriFormatException e) { EventMetadata metadata = new EventMetadata(); metadata.Add("Method", nameof(this.TryQueryGVFSConfig)); metadata.Add("Exception", e.ToString()); metadata.Add("Url", gvfsConfigEndpointString); this.Tracer.RelatedError(metadata, "UriFormatException when constructing Uri", Keywords.Network); return(false); } CancellationToken neverCanceledToken = new CancellationToken(canceled: false); long requestId = HttpRequestor.GetNewRequestId(); RetryWrapper <GVFSConfig> retrier = new RetryWrapper <GVFSConfig>(this.RetryConfig.MaxAttempts, neverCanceledToken); retrier.OnFailure += RetryWrapper <GVFSConfig> .StandardErrorHandler(this.Tracer, requestId, "QueryGvfsConfig"); RetryWrapper <GVFSConfig> .InvocationResult output = retrier.Invoke( tryCount => { GitEndPointResponseData response = this.SendRequest( requestId, gvfsConfigEndpoint, HttpMethod.Get, requestContent: null, cancellationToken: neverCanceledToken); if (response.HasErrors) { return(new RetryWrapper <GVFSConfig> .CallbackResult(response.Error, response.ShouldRetry)); } try { using (StreamReader reader = new StreamReader(response.Stream)) { string configString = reader.RetryableReadToEnd(); GVFSConfig config = JsonConvert.DeserializeObject <GVFSConfig>(configString); return(new RetryWrapper <GVFSConfig> .CallbackResult(config)); } } catch (JsonReaderException e) { return(new RetryWrapper <GVFSConfig> .CallbackResult(e, false)); } }); if (output.Succeeded) { gvfsConfig = output.Result; return(true); } return(false); }
public bool TryQueryGVFSConfig(bool logErrors, out ServerGVFSConfig serverGVFSConfig, out HttpStatusCode?httpStatus, out string errorMessage) { serverGVFSConfig = null; httpStatus = null; errorMessage = null; Uri gvfsConfigEndpoint; string gvfsConfigEndpointString = this.repoUrl + GVFSConstants.Endpoints.GVFSConfig; try { gvfsConfigEndpoint = new Uri(gvfsConfigEndpointString); } catch (UriFormatException e) { EventMetadata metadata = new EventMetadata(); metadata.Add("Method", nameof(this.TryQueryGVFSConfig)); metadata.Add("Exception", e.ToString()); metadata.Add("Url", gvfsConfigEndpointString); this.Tracer.RelatedError(metadata, "UriFormatException when constructing Uri", Keywords.Network); return(false); } long requestId = HttpRequestor.GetNewRequestId(); RetryWrapper <ServerGVFSConfig> retrier = new RetryWrapper <ServerGVFSConfig>(this.RetryConfig.MaxAttempts, CancellationToken.None); if (logErrors) { retrier.OnFailure += RetryWrapper <ServerGVFSConfig> .StandardErrorHandler(this.Tracer, requestId, "QueryGvfsConfig"); } RetryWrapper <ServerGVFSConfig> .InvocationResult output = retrier.Invoke( tryCount => { using (GitEndPointResponseData response = this.SendRequest( requestId, gvfsConfigEndpoint, HttpMethod.Get, requestContent: null, cancellationToken: CancellationToken.None)) { if (response.HasErrors) { return(new RetryWrapper <ServerGVFSConfig> .CallbackResult(response.Error, response.ShouldRetry)); } try { string configString = response.RetryableReadToEnd(); ServerGVFSConfig config = JsonConvert.DeserializeObject <ServerGVFSConfig>(configString); return(new RetryWrapper <ServerGVFSConfig> .CallbackResult(config)); } catch (JsonReaderException e) { return(new RetryWrapper <ServerGVFSConfig> .CallbackResult(e, shouldRetry: false)); } } }); if (output.Succeeded) { serverGVFSConfig = output.Result; httpStatus = HttpStatusCode.OK; return(true); } GitObjectsHttpException httpException = output.Error as GitObjectsHttpException; if (httpException != null) { httpStatus = httpException.StatusCode; errorMessage = httpException.Message; } if (logErrors) { this.Tracer.RelatedError( new EventMetadata { { "Exception", output.Error.ToString() } }, $"{nameof(this.TryQueryGVFSConfig)} failed"); } return(false); }