/// <summary>
        /// Execute a request for the given requestModel asynchronously, executing the given callback upon completions
        /// other than cancellations (gui updates for cancellation should be done at point of cancellation).
        /// </summary>
        /// <param name="requestModel"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        public CancellationTokenSource ExecuteAsync(RequestModel requestModel, Action<ResponseModel> callback)
        {
            //todo: add using statements for disposible objects
            var hasCookies = requestModel.RequestHeaders.ContainsKey("cookie");
            var hasExpect100ContinueHeader =
                requestModel.RequestHeaders.ContainsKey("expect") &&
                requestModel.RequestHeaders["expect"].Equals("100-continue", StringComparison.OrdinalIgnoreCase);

            var handler = new HttpClientHandler {
                AllowAutoRedirect = followRedirects,
                UseCookies = hasCookies
            };

            if (!proxyServer.IsBlank()) {
                handler.Proxy = new WebProxy(proxyServer, false); //make second arg a config option.
                handler.UseProxy = true;
            }

            if (enableAutomaticContentDecompression) {
                foreach (var enc in requestModel.AcceptEncodings) {
                    switch (enc) {
                        case "gzip":
                            handler.AutomaticDecompression |= DecompressionMethods.GZip;
                            break;
                        case "deflate":
                            handler.AutomaticDecompression |= DecompressionMethods.Deflate;
                            break;
                        default:
                            log.Warn("Automatic decompression of '{0}' content specified by the Accept-Encoding request header is not supported", enc);
                            break;
                    }
                }
            }

            var client = new HttpClient(handler);
            var request = new HttpRequestMessage {
                RequestUri = requestModel.Url,
                Method = requestModel.Method
            };

            foreach (var header in requestModel.RequestHeaders) {
                if(header.Key.Equals("cookie", StringComparison.OrdinalIgnoreCase))
                    handler.CookieContainer.SetCookies(requestModel.Url, header.Value.Replace("; ", ", "));
                else
                    request.Headers.Add(header.Key, header.Value);
            }

            request.Headers.ExpectContinue = hasExpect100ContinueHeader;

            if (!requestModel.Url.UserInfo.IsBlank()) {
                var userInfoBase64Text = Base64EncodeUrlUserInfo(requestModel.Url);
                request.Headers.Authorization = new AuthenticationHeaderValue("Basic", userInfoBase64Text);
            }

            if (requestModel.Method != HttpMethod.Get && requestModel.Method != HttpMethod.Head && !requestModel.Body.IsBlank()) {
                //default content-type: http://mattryall.net/blog/2008/03/default-content-type
                string textCt;
                requestModel.ContentHeaders.TryGetValue("Content-Type", out textCt);
                textCt = textCt.IsBlank() ? defaultRequestContentType : textCt; //then try settings supplied
                textCt = textCt.IsBlank() ? "application/octet-stream" : textCt; // then try w3 spec default

                //get encoding
                var ct = new ContentType(textCt);
                //write content w/ BOM  if needed
                var contentBytes = GetEncodedBytes(requestModel.Body, ct.CharSet, includeUtf8Bom);
                var content = new ByteArrayContent(contentBytes);

                foreach (var header in requestModel.ContentHeaders) {
                    content.Headers.Remove(header.Key); //remove defaults
                    if (String.Equals(header.Key, "content-type", StringComparison.OrdinalIgnoreCase)) //treat special w/ defaults, etc.
                        content.Headers.Add(header.Key, textCt);
                    else
                        content.Headers.Add(header.Key, header.Value);
                }

                request.Content = content;
            }

            var start = DateTime.Now;

            var ctokenSource = new CancellationTokenSource();
            var ctoken = ctokenSource.Token;

            client.SendAsync(request,ctoken).ContinueWith(responseTask => {
                try {
                    var end = DateTime.Now;
                    switch (responseTask.Status) {
                        case TaskStatus.RanToCompletion: {
                                var response = responseTask.Result;
                                var responseModel = new ResponseModel(response, start, end);
                                callback(responseModel);
                                break;
                            }
                        case TaskStatus.Canceled: {
                                log.Info("request canceled by user");
                                break;
                            }
                        case TaskStatus.Faulted: {
                                var aggException = responseTask.Exception.Flatten();

                                foreach (var exception in aggException.InnerExceptions)
                                    log.Error("request terminated with an error", exception);

                                string errMessage = String.Join(Environment.NewLine, aggException.InnerExceptions);
                                var responseModel = new ResponseModel(errMessage, start, end);
                                callback(responseModel);
                                break;
                            }
                        default: {
                                var errMessage = String.Format("The request terminated with an unexpected status={0}", responseTask.Status);
                                log.Warn(errMessage);
                                var responseModel = new ResponseModel(errMessage, start, end);
                                callback(responseModel);
                                break;
                            }
                    }
                } catch(Exception ex) {
                    log.Error("exception raised in request continuation, application will proceed in a corrupt state until Task is disposed, at which point the application will shut down with a fatal exception", ex);
                    throw;
                }
            });

            return ctokenSource;
        }
        public static List<string> TryCreate(RequestViewModel vm, out RequestModel requestModel)
        {
            var validationErrors = new List<string>();

            Uri url = null;
            if(vm.Url.IsBlank())
                validationErrors.Add("Request URL may not be empty");
            else {
                var forgivingUrl = vm.Url.Contains("://") ? vm.Url : "http://" + vm.Url;
                if(!Uri.TryCreate(forgivingUrl, UriKind.Absolute, out url))
                    validationErrors.Add("Request URL is invalid");
            }

            var requestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            var contentHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            var acceptEncodings = new HashSet<string>();
            foreach (var line in vm.Headers) {
                if (line.IsBlank())
                    continue; //allow empty lines

                var match = Regex.Match(line, @"^([0-9a-zA-Z-]+)\s*\:(.*)$", RegexOptions.Compiled);
                if (!match.Success)
                    validationErrors.Add("Invalid header line (format incorrect): " + line);
                else {

                    var key = match.Groups[1].Value.Trim();
                    var value = match.Groups[2].Value.Trim();
                    if (requestHeaders.ContainsKey(key) || contentHeaders.ContainsKey(key))
                        validationErrors.Add("Invalid header line (duplicate key, comma-separate multiple values for one key): " + line);
                    else if (String.Equals(key, "authorization", StringComparison.OrdinalIgnoreCase) && !url.UserInfo.IsBlank()) {
                        validationErrors.Add("Invalid header line (Authorization header cannot be specified when user information is given in the url): " + line);
                    } else {
                        //var values = value.Split(',').Select(x => x.Trim()).ToList().AsReadOnly();
                        //some ugliness to leverage system.net.http request and content header validation
                        var hrhValidator = (HttpRequestHeaders)Activator.CreateInstance(typeof(HttpRequestHeaders), true);
                        try {
                            hrhValidator.Add(key, value);
                            requestHeaders.Add(key, value);
                            if(key.Equals("accept-encoding", StringComparison.OrdinalIgnoreCase)) {
                                var encodings = value.Split(',').Select(x => x.Trim().ToLower()).Where(x => x != "");
                                foreach(var enc in encodings)
                                    acceptEncodings.Add(enc);
                            }
                        } catch (InvalidOperationException) { //i.e. header belongs in content headers
                            var hchValidator = (HttpContentHeaders)Activator.CreateInstance(typeof(HttpContentHeaders), BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.NonPublic, null, new[] { (object)(Func<long?>)(() => (long?)null) }, CultureInfo.CurrentCulture);
                            try {
                                hchValidator.Add(key, value);
                                contentHeaders.Add(key, value);
                            } catch (Exception e) {
                                validationErrors.Add(string.Format("Invalid header line ({0}): {1}", e.Message, line));
                            }
                        } catch (Exception e) {
                            validationErrors.Add(string.Format("Invalid header line ({0}): {1}", e.Message, line));
                        }
                    }
                }
            }

            if (validationErrors.Count > 0)
                requestModel = null;
            else {
                requestModel = new RequestModel() {
                    Url = url,
                    Method = new HttpMethod(vm.Method),
                    RequestHeaders = requestHeaders,
                    ContentHeaders = contentHeaders,
                    AcceptEncodings = acceptEncodings,
                    Body = vm.Body
                };
            }

            return validationErrors;
        }
        /// <summary>
        /// Execute a request for the given requestModel asynchronously, executing the given callback upon completions
        /// other than cancellations (gui updates for cancellation should be done at point of cancellation).
        /// </summary>
        /// <param name="requestModel"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        public CancellationTokenSource ExecuteAsync(RequestModel requestModel, Action <ResponseModel> callback)
        {
            //todo: add using statements for disposible objects

            var handler = new HttpClientHandler
            {
                AllowAutoRedirect = followRedirects
            };

            if (!proxyServer.IsBlank())
            {
                handler.Proxy    = new WebProxy(proxyServer, false); //make second arg a config option.
                handler.UseProxy = true;
            }

            var client  = new HttpClient(handler);
            var request = new HttpRequestMessage
            {
                RequestUri = requestModel.Url,
                Method     = requestModel.Method
            };

            foreach (var header in requestModel.RequestHeaders)
            {
                request.Headers.Add(header.Key, header.Value);
            }

            if (requestModel.Method != HttpMethod.Get && requestModel.Method != HttpMethod.Head)
            {
                //default content-type: http://mattryall.net/blog/2008/03/default-content-type
                string textCt;
                requestModel.ContentHeaders.TryGetValue("Content-Type", out textCt);
                textCt = textCt.IsBlank() ? defaultRequestContentType : textCt;  //then try settings supplied
                textCt = textCt.IsBlank() ? "application/octet-stream" : textCt; // then try w3 spec default

                //get encoding
                var ct = new ContentType(textCt);
                //write content w/ BOM  if needed
                var contentBytes = GetEncodedBytes(requestModel.Body, ct.CharSet, includeUtf8Bom);
                var content      = new ByteArrayContent(contentBytes);

                foreach (var header in requestModel.ContentHeaders)
                {
                    content.Headers.Remove(header.Key);                                                //remove defaults
                    if (String.Equals(header.Key, "content-type", StringComparison.OrdinalIgnoreCase)) //treat special w/ defaults, etc.
                    {
                        content.Headers.Add(header.Key, textCt);
                    }
                    else
                    {
                        content.Headers.Add(header.Key, header.Value);
                    }
                }

                request.Content = content;
            }

            var start = DateTime.Now;

            var ctokenSource = new CancellationTokenSource();
            var ctoken       = ctokenSource.Token;

            client.SendAsync(request, ctoken).ContinueWith(responseTask =>
            {
                try
                {
                    var end = DateTime.Now;
                    switch (responseTask.Status)
                    {
                    case TaskStatus.RanToCompletion:
                        {
                            var response      = responseTask.Result;
                            var responseModel = new ResponseModel(response, start, end);
                            callback(responseModel);
                            break;
                        }

                    case TaskStatus.Canceled:
                        {
                            log.Info("request canceled by user");
                            break;
                        }

                    case TaskStatus.Faulted:
                        {
                            var aggException = responseTask.Exception.Flatten();

                            foreach (var exception in aggException.InnerExceptions)
                            {
                                log.ErrorException("request terminated with an error", exception);
                            }

                            string errMessage = String.Join(Environment.NewLine, aggException.InnerExceptions);
                            var responseModel = new ResponseModel(errMessage, start, end);
                            callback(responseModel);
                            break;
                        }

                    default:
                        {
                            var errMessage = String.Format("The request terminated with an unexpected status={0}", responseTask.Status);
                            log.Warn(errMessage);
                            var responseModel = new ResponseModel(errMessage, start, end);
                            callback(responseModel);
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    log.ErrorException("exception raised in request continuation, application will proceed in a corrupt state until Task is disposed, at which point the application will shut down with a fatal exception", ex);
                    throw;
                }
            });

            return(ctokenSource);
        }
        public static List <string> TryCreate(RequestViewModel vm, out RequestModel requestModel)
        {
            var validationErrors = new List <string>();

            Uri url = null;

            if (vm.Url.IsBlank())
            {
                validationErrors.Add("Request URL may not be empty");
            }
            else
            {
                var forgivingUrl = vm.Url.Contains("://") ? vm.Url : "http://" + vm.Url;
                if (!Uri.TryCreate(forgivingUrl, UriKind.Absolute, out url))
                {
                    validationErrors.Add("Request URL is invalid");
                }
            }

            var requestHeaders  = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
            var contentHeaders  = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
            var acceptEncodings = new HashSet <string>();

            foreach (var line in vm.Headers)
            {
                if (line.IsBlank())
                {
                    continue; //allow empty lines
                }
                var match = Regex.Match(line, @"^([0-9a-zA-Z-]+)\s*\:(.*)$", RegexOptions.Compiled);
                if (!match.Success)
                {
                    validationErrors.Add("Invalid header line (format incorrect): " + line);
                }
                else
                {
                    var key   = match.Groups[1].Value.Trim();
                    var value = match.Groups[2].Value.Trim();
                    if (requestHeaders.ContainsKey(key) || contentHeaders.ContainsKey(key))
                    {
                        validationErrors.Add("Invalid header line (duplicate key, comma-separate multiple values for one key): " + line);
                    }
                    else if (String.Equals(key, "authorization", StringComparison.OrdinalIgnoreCase) && !url.UserInfo.IsBlank())
                    {
                        validationErrors.Add("Invalid header line (Authorization header cannot be specified when user information is given in the url): " + line);
                    }
                    else
                    {
                        //var values = value.Split(',').Select(x => x.Trim()).ToList().AsReadOnly();
                        //some ugliness to leverage system.net.http request and content header validation
                        var hrhValidator = (HttpRequestHeaders)Activator.CreateInstance(typeof(HttpRequestHeaders), true);
                        try {
                            hrhValidator.Add(key, value);
                            requestHeaders.Add(key, value);
                            if (key.Equals("accept-encoding", StringComparison.OrdinalIgnoreCase))
                            {
                                var encodings = value.Split(',').Select(x => x.Trim().ToLower()).Where(x => x != "");
                                foreach (var enc in encodings)
                                {
                                    acceptEncodings.Add(enc);
                                }
                            }
                        } catch (InvalidOperationException) { //i.e. header belongs in content headers
                            var hchValidator = (HttpContentHeaders)Activator.CreateInstance(typeof(HttpContentHeaders), BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.NonPublic, null, new[] { (object)(Func <long?>)(() => (long?)null) }, CultureInfo.CurrentCulture);
                            try {
                                hchValidator.Add(key, value);
                                contentHeaders.Add(key, value);
                            } catch (Exception e) {
                                validationErrors.Add(string.Format("Invalid header line ({0}): {1}", e.Message, line));
                            }
                        } catch (Exception e) {
                            validationErrors.Add(string.Format("Invalid header line ({0}): {1}", e.Message, line));
                        }
                    }
                }
            }

            if (validationErrors.Count > 0)
            {
                requestModel = null;
            }
            else
            {
                requestModel = new RequestModel()
                {
                    Url             = url,
                    Method          = new HttpMethod(vm.Method),
                    RequestHeaders  = requestHeaders,
                    ContentHeaders  = contentHeaders,
                    AcceptEncodings = acceptEncodings,
                    Body            = vm.Body
                };
            }

            return(validationErrors);
        }
Exemple #5
0
        public static List <string> TryCreate(RequestViewModel vm, out RequestModel requestModel)
        {
            var validationErrors = new List <string>();

            Uri url = null;

            if (vm.Url.IsBlank())
            {
                validationErrors.Add("Request URL may not be empty");
            }
            else
            {
                var knownProtocals = new[] { "http://", "https://", "ftp://", "ftps://", "file:///" };
                var forgivingUrl   = knownProtocals.Any(x => vm.Url.StartsWith(x)) ? vm.Url : "http://" + vm.Url;
                if (!Uri.TryCreate(forgivingUrl, UriKind.Absolute, out url))
                {
                    validationErrors.Add("Request URL is invalid");
                }
            }

            var requestHeaders = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
            var contentHeaders = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            foreach (var line in vm.Headers)
            {
                if (line.IsBlank())
                {
                    continue; //allow empty lines
                }
                var match = Regex.Match(line, @"^([^\:]+)\:(.+)$", RegexOptions.Compiled);
                if (!match.Success)
                {
                    validationErrors.Add("Invalid header line (format incorrect): " + line);
                }
                else
                {
                    var key   = match.Groups[1].Value.Trim();
                    var value = match.Groups[2].Value.Trim();
                    if (key.IsBlank() || value.IsBlank())
                    {
                        validationErrors.Add("Invalid header line (key or value is blank): " + line);
                    }
                    else if (requestHeaders.ContainsKey(key) || contentHeaders.ContainsKey(key))
                    {
                        validationErrors.Add("Invalid header line (duplicate key, comma-separate multiple values for one key): " + line);
                    }
                    else
                    {
                        //var values = value.Split(',').Select(x => x.Trim()).ToList().AsReadOnly();
                        //some ugliness to leverage system.net.http request and content header validation
                        var hrhValidator = (HttpRequestHeaders)Activator.CreateInstance(typeof(HttpRequestHeaders), true);
                        try {
                            hrhValidator.Add(key, value);
                            requestHeaders.Add(key, value);
                        } catch (InvalidOperationException) { //i.e. header belongs in content headers
                            var hchValidator = (HttpContentHeaders)Activator.CreateInstance(typeof(HttpContentHeaders), BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.NonPublic, null, new[] { (object)(Func <long?>)(() => (long?)null) }, CultureInfo.CurrentCulture);
                            try {
                                hchValidator.Add(key, value);
                                contentHeaders.Add(key, value);
                            } catch (Exception e) {
                                validationErrors.Add(string.Format("Invalid header line ({0}): {1}", e.Message, line));
                            }
                        } catch (Exception e) {
                            validationErrors.Add(string.Format("Invalid header line ({0}): {1}", e.Message, line));
                        }
                    }
                }
            }

            if (validationErrors.Count > 0)
            {
                requestModel = null;
            }
            else
            {
                requestModel = new RequestModel()
                {
                    Url            = url,
                    Method         = new HttpMethod(vm.Method),
                    RequestHeaders = requestHeaders,
                    ContentHeaders = contentHeaders,
                    Body           = vm.Body
                };
            }

            return(validationErrors);
        }
Exemple #6
0
        /// <summary>
        /// Execute a request for the given requestModel asynchronously, executing the given callback upon completions
        /// other than cancellations (gui updates for cancellation should be done at point of cancellation).
        /// </summary>
        /// <param name="requestModel"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        public CancellationTokenSource ExecuteAsync(RequestModel requestModel, Action <ResponseModel> callback)
        {
            //todo: add using statements for disposible objects
            var hasCookies = requestModel.RequestHeaders.ContainsKey("cookie");
            var hasExpect100ContinueHeader =
                requestModel.RequestHeaders.ContainsKey("expect") &&
                requestModel.RequestHeaders["expect"].Equals("100-continue", StringComparison.OrdinalIgnoreCase);

            var handler = new HttpClientHandler {
                AllowAutoRedirect     = followRedirects,
                UseCookies            = hasCookies,
                UseDefaultCredentials = true
            };

            if (!proxyServer.IsBlank())
            {
                handler.Proxy    = new WebProxy(proxyServer, false); //make second arg a config option.
                handler.UseProxy = true;
            }

            if (enableAutomaticContentDecompression)
            {
                foreach (var enc in requestModel.AcceptEncodings)
                {
                    switch (enc)
                    {
                    case "gzip":
                        handler.AutomaticDecompression |= DecompressionMethods.GZip;
                        break;

                    case "deflate":
                        handler.AutomaticDecompression |= DecompressionMethods.Deflate;
                        break;

                    default:
                        log.Warn("Automatic decompression of '{0}' content specified by the Accept-Encoding request header is not supported", enc);
                        break;
                    }
                }
            }

            var client  = new HttpClient(handler);
            var request = new HttpRequestMessage {
                RequestUri = requestModel.Url,
                Method     = requestModel.Method
            };

            foreach (var header in requestModel.RequestHeaders)
            {
                if (header.Key.Equals("cookie", StringComparison.OrdinalIgnoreCase))
                {
                    handler.CookieContainer.SetCookies(requestModel.Url, header.Value.Replace("; ", ", "));
                }
                else
                {
                    request.Headers.Add(header.Key, header.Value);
                }
            }

            request.Headers.ExpectContinue = hasExpect100ContinueHeader;

            if (!requestModel.Url.UserInfo.IsBlank())
            {
                var userInfoBase64Text = Base64EncodeUrlUserInfo(requestModel.Url);
                request.Headers.Authorization = new AuthenticationHeaderValue("Basic", userInfoBase64Text);
            }

            if (requestModel.Method != HttpMethod.Get && requestModel.Method != HttpMethod.Head && !requestModel.Body.IsBlank())
            {
                //default content-type: http://mattryall.net/blog/2008/03/default-content-type
                string textCt;
                requestModel.ContentHeaders.TryGetValue("Content-Type", out textCt);
                textCt = textCt.IsBlank() ? defaultRequestContentType : textCt;  //then try settings supplied
                textCt = textCt.IsBlank() ? "application/octet-stream" : textCt; // then try w3 spec default

                //get encoding
                var ct = new ContentType(textCt);
                //write content w/ BOM  if needed
                var contentBytes = GetEncodedBytes(requestModel.Body, ct.CharSet, includeUtf8Bom);
                var content      = new ByteArrayContent(contentBytes);

                foreach (var header in requestModel.ContentHeaders)
                {
                    content.Headers.Remove(header.Key);                                                //remove defaults
                    if (String.Equals(header.Key, "content-type", StringComparison.OrdinalIgnoreCase)) //treat special w/ defaults, etc.
                    {
                        content.Headers.Add(header.Key, textCt);
                    }
                    else
                    {
                        content.Headers.Add(header.Key, header.Value);
                    }
                }

                request.Content = content;
            }

            var start = DateTime.Now;

            var ctokenSource = new CancellationTokenSource();
            var ctoken       = ctokenSource.Token;

            client.SendAsync(request, ctoken).ContinueWith(responseTask => {
                try {
                    var end = DateTime.Now;
                    switch (responseTask.Status)
                    {
                    case TaskStatus.RanToCompletion: {
                        var response      = responseTask.Result;
                        var responseModel = new ResponseModel(response, start, end);
                        callback(responseModel);
                        break;
                    }

                    case TaskStatus.Canceled: {
                        log.Info("request canceled by user");
                        break;
                    }

                    case TaskStatus.Faulted: {
                        var aggException = responseTask.Exception.Flatten();

                        foreach (var exception in aggException.InnerExceptions)
                        {
                            log.Error(exception, "request terminated with an error");
                        }

                        string errMessage = String.Join(Environment.NewLine, aggException.InnerExceptions);
                        var responseModel = new ResponseModel(errMessage, start, end);
                        callback(responseModel);
                        break;
                    }

                    default: {
                        var errMessage = String.Format("The request terminated with an unexpected status={0}", responseTask.Status);
                        log.Warn(errMessage);
                        var responseModel = new ResponseModel(errMessage, start, end);
                        callback(responseModel);
                        break;
                    }
                    }
                } catch (Exception ex) {
                    log.Error(ex, "exception raised in request continuation, application will proceed in a corrupt state until Task is disposed, at which point the application will shut down with a fatal exception");
                    throw;
                }
            });

            return(ctokenSource);
        }