Exemple #1
0
        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);
        }
Exemple #3
0
        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);
        }