using Apewer; using Apewer.Internals; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Security; using System.Security; using System.Security.Cryptography.X509Certificates; using System.Text; namespace Apewer.Network { /// public class HttpClient { string _key = TextUtility.Key(); int _block = 1024; /// public string Key { get => _key; } /// 获取进度的块大小,以字节为单位,默认值:1024(1KB)。 public int Block { get => _block; set => NumberUtility.Restrict(value, 1, 1048576); } /// 构建 的实例。 public HttpClient() { RequestHeaders = new StringPairs(); } #region request /// 获取或设置将要请求的方法,默认为 GET。 public HttpMethod Method { get; set; } /// 获取或设置将要请求的地址。 public string Url { get; set; } /// 获取或设置请求主体的超时毫秒数,默认值取决于运行时(100,000 毫秒)。 public int Timeout { get; set; } /// 获取或设置请求使用的证书。 public X509Certificate ClientCertificate { get; set; } /// 获取或设置请求的自定义头。 public StringPairs RequestHeaders { get; set; } /// 获取或设置请求的 Cookies。 public Cookie[] RequestCookies { get; set; } /// 获取或设置 POST 请求发送的数据。当 Stream 属性有效时此属性将被忽略。 public byte[] RequestData { get; set; } /// 获取或设置 POST 请求发送的数据。此属性有效时将忽略 Data 属性。 public Stream RequestStream { get; set; } /// 获取或设置写入请求主体的进度回调。 public Action RequestProgress { get; set; } /// 获取或设置请求的内容类型,默认为空。此属性可能会被自定义头取代。 public string RequestContentType { get; set; } /// 获取或设置请求的内容字节长度,此属性仅在指定大于 0 时被使用。此属性可能会被自定义头或主体字节数组取代。 public long RequestContentLength { get; set; } /// 获取或设置用户代理。此属性可能会被自定义头取代。 public string UserAgent { get; set; } /// 获取或设置来源。此属性可能会被自定义头取代。 public string Referer { get; set; } #endregion #region response /// 跟随响应的重定向。 /// 默认值:False public bool AllowRedirect { get; set; } /// 获取或设置要接收响应主体的流。默认为 NULL 值。 /// 指定为 NULL 时,响应体将写入字节数组;
非 NULL 时,响应体将写入此流,并忽略 ResponseData 属性。
public Stream ResponseStream { get; set; } /// 获取或设置读取响应主体的进度回调 public Action ResponseProgress { get; set; } /// 获取响应的状态。 public string ResponseStatus { get; private set; } /// 获取响应的缓存状态。 public bool ResponseCached { get; private set; } /// 获取响应的头。 public Cookie[] ResponseCookies { get; private set; } /// 获取响应的头。 public StringPairs ResponseHeaders { get; private set; } /// 获取响应的主体,当指定流时此属性将不被使用。 public byte[] ResponseData { get; private set; } #endregion #region send object _locker = new object(); Exception _exception = null; HttpWebRequest _request = null; HttpWebResponse _response = null; Nullable _request_time = null; Nullable _response_time = null; /// 发送请求的时间。 public Nullable RequestTime { get => _request_time; } /// 接收响应的时间。 public Nullable ResponseTime { get => _response_time; } /// 获取最近发生的异常。 public Exception Exception { get { return _exception; } } Exception Return(Exception ex) { _exception = ex; return ex; } /// 发送请求,并获取响应。 /// 捕获发生的异常,将异常作为返回值。 /// 发生的异常。 public Exception Send(bool catchEx = true) { lock (_locker) { _exception = null; ResponseStatus = default; ResponseCached = default; ResponseCookies = default; ResponseHeaders = default; ResponseData = default; var url = Url; if (url.IsEmpty()) { var ex = new MissingMemberException("未指定 URL。"); if (!catchEx) throw ex; return Return(ex); } var method = Method; if (method == HttpMethod.NULL) { if (RequestData != null || RequestStream != null) method = HttpMethod.POST; else method = HttpMethod.GET; } if (catchEx) { try { _request = Prepare(url, method); _request_time = DateTime.Now; _response = _request.GetResponse() as HttpWebResponse; _response_time = DateTime.Now; Parse(_response); } catch (Exception ex) { _response_time = DateTime.Now; _exception = ex; if (ex is WebException webEx) Parse((HttpWebResponse)webEx.Response); return ex; } } else { _request = Prepare(url, method); _request_time = DateTime.Now; _response = _request.GetResponse() as HttpWebResponse; _response_time = DateTime.Now; Parse(_response); } } return null; } /// /// /// /// /// /// /// /// HttpWebRequest Prepare(string url, HttpMethod method) { var https = url.ToLower().StartsWith("https"); if (https) SslUtility.ApproveValidation(); var request = (HttpWebRequest)WebRequest.Create(url); var certificate = ClientCertificate; if (certificate != null) request.ClientCertificates.Add(certificate); var timeout = Timeout; if (timeout > 0) request.Timeout = timeout; request.Method = method.ToString(); request.AllowAutoRedirect = AllowRedirect; request.CookieContainer = new CookieContainer(); var cookies = RequestCookies; if (cookies != null) { foreach (var cookie in cookies) { if (cookie == null) continue; request.CookieContainer.Add(cookie); } } var length = RequestContentLength; var type = RequestContentType; var ua = UserAgent; var r = Referer; var headers = RequestHeaders; if (headers != null) foreach (var header in headers) { var key = header.Key.ToTrim(); ; var value = header.Value.ToTrim(); if (TextUtility.IsBlank(key)) continue; if (TextUtility.IsBlank(value)) continue; try { switch (key.Lower()) { case "accept": request.Accept = value; break; case "connection": request.Connection = value; break; case "content-length": case "contentlength": length = Math.Max(length, NumberUtility.Int64(value)); break; case "content-type": case "contenttype": type = value; break; case "expect": request.Expect = value; break; #if !NET20 case "host": request.Host = value; break; #endif case "if-modified-since": var requestIfModifiedSince = ClockUtility.Parse(value); if (requestIfModifiedSince != null) request.IfModifiedSince = requestIfModifiedSince.Value; break; case "referer": case "referrer": r = value; break; case "transfer-encoding": request.TransferEncoding = value; break; case "user-agent": case "useragent": ua = value; break; default: request.Headers.Add(header.Key, header.Value); break; } } catch { } } if (length > 0L) request.ContentLength = length; if (type.NotEmpty()) request.ContentType = type; if (ua.NotEmpty()) request.UserAgent = ua; if (r.NotEmpty()) request.Referer = r; if (method == HttpMethod.POST) { var stream = RequestStream; var data = RequestData; if (stream != null) { var body = request.GetRequestStream(); BytesUtility.Read(stream, body, RequestProgress, Block); } else if (data != null) { request.ContentLength = data.LongLength; var body = request.GetRequestStream(); BytesUtility.Write(body, data, RequestProgress, Block); } } return request; } /// /// /// /// /// /// void Parse(HttpWebResponse response) { if (response == null) return; ResponseStatus = ((int)response.StatusCode).ToString(); ResponseCached = response.IsFromCache; var headers = new StringPairs(); foreach (var key in response.Headers.AllKeys) { var values = response.Headers.GetValues(key); headers.Add(key, values.Join(",")); } ResponseHeaders = headers; var cb = new ArrayBuilder(); foreach (var item in response.Cookies) { var cookie = item as Cookie; if (cookie == null) continue; cb.Add(cookie); } var body = response.GetResponseStream(); var progress = ResponseProgress; var hasProgress = progress != null; var stream = ResponseStream; if (stream == null) ResponseData = BytesUtility.Read(body); else BytesUtility.Read(body, stream, progress, Block); response.Close(); } #endregion #region static const int SimpleTimeout = 30000; /// GET public static HttpClient Get(string url, int timeout = 30000) { var http = new HttpClient(); http.Url = url; http.Timeout = timeout; http.Method = HttpMethod.GET; http.Send(); return http; } /// POST public static HttpClient Post(string url, byte[] data, int timeout = 30000, string type = "application/octet-stream") { var http = new HttpClient(); http.Url = url; http.Timeout = timeout; http.Method = HttpMethod.POST; if (!string.IsNullOrEmpty(type)) http.RequestContentType = type; if (data != null) { http.RequestContentLength = data.LongLength; http.RequestData = data; } http.Send(); return http; } /// POST text/plain public static HttpClient Text(string url, string text, int timeout = 30000, string type = "text/plain") { return Post(url, TextUtility.Bytes(text), timeout, type); } /// POST application/x-www-form-urlencoded public static HttpClient Form(string url, IDictionary form, int timeout = 30000) { if (form == null) return Post(url, BytesUtility.Empty, timeout, "application/x-www-form-urlencoded"); var cache = new List(); foreach (var i in form) { var key = TextUtility.EncodeUrl(i.Key); var value = TextUtility.EncodeUrl(i.Value); cache.Add(key + "=" + value); } var text = string.Join("&", cache.ToArray()); var data = TextUtility.Bytes(text); return Post(url, data, timeout, "application/x-www-form-urlencoded"); } /// POST application/x-www-form-urlencoded public static HttpClient Form(string url, Dictionary form, int timeout = 30000) => Form(url, form as IDictionary, timeout); /// 合并表单参数,不包含 Query 的 ? 符号。 public static string MergeForm(Dictionary form) { if (form == null) return ""; if (form.Count < 1) return ""; var cache = new List(); foreach (var i in form) { var key = TextUtility.EncodeUrl(i.Key); var value = TextUtility.EncodeUrl(i.Value); cache.Add(key + "=" + value); } var text = string.Join("&", cache.ToArray()); return text; } #endregion } }