public bool TryQueryRepoInfo(bool logErrors, out VstsInfoData vstsInfo, out string errorMessage) { Uri repoInfoEndpoint; if (!this.TryCreateRepoEndpointUri(this.repoUrl, ScalarConstants.Endpoints.RepoInfo, out repoInfoEndpoint, out errorMessage)) { vstsInfo = null; return(false); } long requestId = HttpRequestor.GetNewRequestId(); RetryWrapper <VstsInfoData> retrier = new RetryWrapper <VstsInfoData>(this.RetryConfig.MaxAttempts, CancellationToken.None); retrier.OnFailure += RetryWrapper <VstsInfoData> .StandardErrorHandler( this.Tracer, requestId, "QueryVstsInfo", forceLogAsWarning : true); // Not all servers support /vsts/info RetryWrapper <VstsInfoData> .InvocationResult output = retrier.Invoke( tryCount => { using (GitEndPointResponseData response = this.SendRequest( requestId, repoInfoEndpoint, HttpMethod.Get, requestContent: null, cancellationToken: CancellationToken.None)) { if (response.HasErrors) { return(new RetryWrapper <VstsInfoData> .CallbackResult(response.Error, response.ShouldRetry)); } try { string configString = response.RetryableReadToEnd(); VstsInfoData vstsInfoData = JsonConvert.DeserializeObject <VstsInfoData>( configString, new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore }); return(new RetryWrapper <VstsInfoData> .CallbackResult(vstsInfoData)); } catch (JsonReaderException e) { return(new RetryWrapper <VstsInfoData> .CallbackResult(e, shouldRetry: false)); } } }); if (output.Succeeded) { vstsInfo = output.Result; errorMessage = null; return(true); } GitObjectsHttpException httpException = output.Error as GitObjectsHttpException; HttpStatusCode? httpStatusCode = httpException?.StatusCode; vstsInfo = null; EventMetadata metadata = new EventMetadata(); metadata.Add(nameof(httpStatusCode), httpStatusCode.ToString()); metadata.Add(nameof(this.IsAnonymous), this.IsAnonymous); if (httpStatusCode == HttpStatusCode.NotFound || (httpStatusCode == HttpStatusCode.Unauthorized && this.IsAnonymous)) { errorMessage = null; this.Tracer.RelatedEvent( EventLevel.Informational, $"{nameof(this.TryQueryRepoInfo)}_NoVstsInfo", metadata); // These failures are OK because not all servers support /vsts/info return(true); } metadata.Add("Exception", output.Error.ToString()); this.Tracer.RelatedError(metadata, $"{nameof(this.TryQueryRepoInfo)} failed"); errorMessage = output.Error.Message; return(false); }
public bool TryQueryScalarConfig(bool logErrors, out ServerScalarConfig serverScalarConfig, out HttpStatusCode?httpStatus, out string errorMessage) { Uri scalarConfigEndpoint; if (!this.TryCreateRepoEndpointUri(this.repoUrl, ScalarConstants.Endpoints.ScalarConfig, out scalarConfigEndpoint, out errorMessage)) { serverScalarConfig = null; httpStatus = null; return(false); } long requestId = HttpRequestor.GetNewRequestId(); RetryWrapper <ServerScalarConfig> retrier = new RetryWrapper <ServerScalarConfig>(this.RetryConfig.MaxAttempts, CancellationToken.None); if (logErrors) { retrier.OnFailure += RetryWrapper <ServerScalarConfig> .StandardErrorHandler(this.Tracer, requestId, "QueryGvfsConfig"); } RetryWrapper <ServerScalarConfig> .InvocationResult output = retrier.Invoke( tryCount => { using (GitEndPointResponseData response = this.SendRequest( requestId, scalarConfigEndpoint, HttpMethod.Get, requestContent: null, cancellationToken: CancellationToken.None)) { if (response.HasErrors) { return(new RetryWrapper <ServerScalarConfig> .CallbackResult(response.Error, response.ShouldRetry)); } try { string configString = response.RetryableReadToEnd(); ServerScalarConfig config = JsonConvert.DeserializeObject <ServerScalarConfig>(configString); return(new RetryWrapper <ServerScalarConfig> .CallbackResult(config)); } catch (JsonReaderException e) { return(new RetryWrapper <ServerScalarConfig> .CallbackResult(e, shouldRetry: false)); } } }); if (output.Succeeded) { serverScalarConfig = output.Result; httpStatus = HttpStatusCode.OK; return(true); } httpStatus = null; GitObjectsHttpException httpException = output.Error as GitObjectsHttpException; if (httpException != null) { httpStatus = httpException.StatusCode; } errorMessage = output.Error.Message; if (logErrors) { this.Tracer.RelatedError( new EventMetadata { { "Exception", output.Error.ToString() } }, $"{nameof(this.TryQueryScalarConfig)} failed"); } serverScalarConfig = null; return(false); }
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); }