You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
346 lines
13 KiB
346 lines
13 KiB
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
|
|
{
|
|
|
|
/// <summary></summary>
|
|
public class HttpClient
|
|
{
|
|
|
|
private string _key = TextGenerator.NewGuid();
|
|
internal bool _locked = false;
|
|
|
|
private TextSet _properties = new TextSet(true);
|
|
private HttpRequest _request = new HttpRequest();
|
|
private HttpResponse _response = new HttpResponse();
|
|
private Exception _lastexception = null;
|
|
private HttpWebRequest _lastrequest = null;
|
|
private HttpWebResponse _lastresponse = null;
|
|
|
|
/// <summary></summary>
|
|
public HttpRequest Request { get { return _request; } }
|
|
|
|
/// <summary></summary>
|
|
public HttpResponse Response { get { return _response; } }
|
|
|
|
/// <summary></summary>
|
|
public string Key { get { return _properties["Key"]; } }
|
|
|
|
/// <exception cref="System.ArgumentException"></exception>
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.InvalidOperationException"></exception>
|
|
/// <exception cref="System.NotSupportedException"></exception>
|
|
/// <exception cref="System.ObjectDisposedException"></exception>
|
|
/// <exception cref="System.Security.SecurityException"></exception>
|
|
/// <exception cref="System.Net.ProtocolViolationException"></exception>
|
|
/// <exception cref="System.Net.WebException"></exception>
|
|
private static HttpWebRequest CreateRequest(HttpRequest request)
|
|
{
|
|
var error = (Exception)null;
|
|
|
|
if (request == null) throw new ArgumentNullException("Request", "参数无效。");
|
|
|
|
var properties = request._properties;
|
|
var certificates = request._certificates;
|
|
var headers = request._headers;
|
|
var cookies = request._cookies;
|
|
var timeout = request._timeout;
|
|
var url = properties["Url"];
|
|
|
|
// 全局证书验证。
|
|
var https = url.ToLower().StartsWith("https");
|
|
if (https) SslUtility.ApproveValidation();
|
|
|
|
// 创建对象。
|
|
var http = (HttpWebRequest)WebRequest.Create(properties["Url"]);
|
|
|
|
// 证书。
|
|
foreach (var certificate in certificates)
|
|
{
|
|
if (certificate == null) continue;
|
|
if (certificate.LongLength < 1L) continue;
|
|
try
|
|
{
|
|
var x509 = new X509Certificate(certificate);
|
|
http.ClientCertificates.Add(x509);
|
|
}
|
|
finally { }
|
|
}
|
|
|
|
// 请求超时。
|
|
if (timeout > 0) http.Timeout = timeout;
|
|
|
|
// 方法。
|
|
http.Method = request._method.ToString();
|
|
|
|
// 允许重定向。
|
|
http.AllowAutoRedirect = request._redirect;
|
|
|
|
// 头。
|
|
foreach (var header in headers.Origin)
|
|
{
|
|
var key = header.Key.SafeTrim(); ;
|
|
var value = header.Value.SafeTrim();
|
|
if (TextVerifier.IsBlank(key)) continue;
|
|
if (TextVerifier.IsBlank(value)) continue;
|
|
try
|
|
{
|
|
switch (key.SafeLower())
|
|
{
|
|
case "accept":
|
|
http.Accept = value;
|
|
break;
|
|
case "connection":
|
|
http.Connection = value;
|
|
break;
|
|
case "content-length":
|
|
http.ContentLength = TextUtility.GetInt64(value);
|
|
break;
|
|
case "content-type":
|
|
http.ContentType = value;
|
|
break;
|
|
case "date":
|
|
// request.Date = DateTimeUtility.Parse(value);
|
|
break;
|
|
case "expect":
|
|
http.Expect = value;
|
|
break;
|
|
#if !NET20
|
|
case "host":
|
|
http.Host = value;
|
|
break;
|
|
#endif
|
|
case "if-modified-since":
|
|
// request.IfModifiedSince = DateTimeUtility.Parse(value);
|
|
break;
|
|
case "referer":
|
|
case "referrer":
|
|
http.Referer = value;
|
|
break;
|
|
case "transfer-encoding":
|
|
http.TransferEncoding = value;
|
|
break;
|
|
case "user-agent":
|
|
http.UserAgent = value;
|
|
break;
|
|
default:
|
|
http.Headers.Add(header.Key, header.Value);
|
|
break;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
if (!TextVerifier.IsBlank(properties["TransferEncoding"])) http.TransferEncoding = properties["TransferEncoding"];
|
|
if (!TextVerifier.IsBlank(properties["ContentType"])) http.ContentType = properties["ContentType"];
|
|
if (!TextVerifier.IsBlank(properties["UserAgent"])) http.UserAgent = properties["UserAgent"];
|
|
if (!TextVerifier.IsBlank(properties["Referer"])) http.Referer = properties["Referer"];
|
|
|
|
// Cookies。
|
|
if (cookies != null) http.CookieContainer = cookies;
|
|
|
|
// 对 POST 请求加入内容数据。
|
|
if (request._method == HttpMethod.POST)
|
|
{
|
|
if (request._stream != null)
|
|
{
|
|
http.ContentLength = request._contentlength;
|
|
var rs = http.GetRequestStream();
|
|
try
|
|
{
|
|
StreamHelper.Read(request._stream, rs, 1024, request._progress);
|
|
}
|
|
catch { }
|
|
}
|
|
else if (request._data != null && request._data.LongLength > 0L)
|
|
{
|
|
http.ContentLength = request._data.Length;
|
|
var rs = http.GetRequestStream();
|
|
try
|
|
{
|
|
rs.Write(request._data, 0, request._data.Length);
|
|
}
|
|
catch (Exception exception) { error = exception; }
|
|
}
|
|
}
|
|
|
|
if (error == null) return http;
|
|
throw error;
|
|
}
|
|
|
|
/// <exception cref="System.ArgumentNullException"></exception>
|
|
/// <exception cref="System.InvalidOperationException"></exception>
|
|
/// <exception cref="System.NotSupportedException"></exception>
|
|
/// <exception cref="System.ObjectDisposedException"></exception>
|
|
/// <exception cref="System.Net.ProtocolViolationException"></exception>
|
|
/// <exception cref="System.Net.WebException"></exception>
|
|
private static HttpWebResponse CreateResponse(HttpWebRequest request, HttpResponse response)
|
|
{
|
|
var error = (Exception)null;
|
|
|
|
// 发起请求。
|
|
var http = (HttpWebResponse)request.GetResponse();
|
|
|
|
// 读取头。
|
|
foreach (var key in http.Headers.AllKeys) response._headers[key] = http.Headers[key];
|
|
response._cached = http.IsFromCache;
|
|
response._cookies = http.Cookies;
|
|
response._contentlength = http.ContentLength;
|
|
response._statuscode = http.StatusCode;
|
|
response._properties["CharacterSet"] = http.CharacterSet;
|
|
response._properties["ContentEncoding"] = http.ContentEncoding;
|
|
response._properties["ContentType"] = http.ContentType;
|
|
response._properties["LastModified"] = ClockValue.Create(http.LastModified).Lucid;
|
|
response._properties["Method"] = http.Method;
|
|
response._properties["ProtocolVersion"] = http.ProtocolVersion.ToString();
|
|
response._properties["Url"] = http.ResponseUri.OriginalString;
|
|
response._properties["Server"] = http.Server;
|
|
response._properties["StatusDescription"] = http.StatusDescription;
|
|
|
|
// 读取内容。
|
|
var rs = http.GetResponseStream();
|
|
if (response._stream == null)
|
|
{
|
|
var memory = new MemoryStream();
|
|
try
|
|
{
|
|
StreamHelper.Read(rs, memory, Constant.DefaultBufferCapacity, response.ProgressCallback);
|
|
}
|
|
catch (Exception exception) { error = exception; }
|
|
response._data = memory.ToArray();
|
|
memory.Dispose();
|
|
}
|
|
else
|
|
{
|
|
StreamHelper.Read(rs, response._stream, 1024, response.ProgressCallback);
|
|
}
|
|
|
|
http.Close();
|
|
|
|
if (error == null) return http;
|
|
throw error;
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public Exception LatestException { get { return _lastexception; } }
|
|
|
|
/// <summary></summary>
|
|
public WebException WebException { get { return _lastexception as WebException; } }
|
|
|
|
/// <summary></summary>
|
|
public Exception Send()
|
|
{
|
|
var error = null as Exception;
|
|
if (_request._locked || _response._locked)
|
|
{
|
|
error = new InvalidOperationException("存在已启动的请求,无法继续此次请求。");
|
|
}
|
|
else
|
|
{
|
|
_request._locked = true;
|
|
_response._locked = true;
|
|
|
|
try
|
|
{
|
|
_lastrequest = CreateRequest(_request);
|
|
_lastresponse = CreateResponse(_lastrequest, _response);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
error = exception;
|
|
}
|
|
|
|
_request._locked = false;
|
|
_response._locked = false;
|
|
}
|
|
_lastexception = error;
|
|
return error;
|
|
}
|
|
|
|
/// <summary>GET</summary>
|
|
public static HttpClient SimpleGet(string url, int timeout = 30000)
|
|
{
|
|
var http = new HttpClient();
|
|
http.Request.Url = url;
|
|
http.Request.Timeout = timeout;
|
|
http.Request.Method = HttpMethod.GET;
|
|
http.Send();
|
|
return http;
|
|
}
|
|
|
|
/// <summary>POST</summary>
|
|
public static HttpClient SimplePost(string url, byte[] data, int timeout = 30000, string type = "application/octet-stream")
|
|
{
|
|
var http = new HttpClient();
|
|
http.Request.Url = url;
|
|
http.Request.Timeout = timeout;
|
|
http.Request.Method = HttpMethod.POST;
|
|
if (type.NotBlank()) http.Request.ContentType = type;
|
|
http.Request.Data = data ?? BinaryUtility.EmptyBytes;
|
|
http.Send();
|
|
return http;
|
|
}
|
|
|
|
/// <summary>POST text/plain</summary>
|
|
public static HttpClient SimpleText(string url, string text, int timeout = 30000, string type = "text/plain")
|
|
{
|
|
return SimplePost(url, TextUtility.ToBinary(text), timeout, type);
|
|
}
|
|
|
|
/// <summary>POST application/x-www-form-urlencoded</summary>
|
|
public static HttpClient SimpleForm(string url, IDictionary<string, string> form, int timeout = 30000)
|
|
{
|
|
if (form == null) return SimplePost(url, BinaryUtility.EmptyBytes, timeout, "application/x-www-form-urlencoded");
|
|
|
|
var cache = new List<string>();
|
|
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.ToBinary(text);
|
|
return SimplePost(url, data, timeout, "application/x-www-form-urlencoded");
|
|
}
|
|
|
|
/// <summary>POST application/x-www-form-urlencoded</summary>
|
|
public static HttpClient SimpleForm(string url, TextSet form, int timeout = 30000)
|
|
{
|
|
return SimpleForm(url, ClassUtility.GetOrigin(form), timeout);
|
|
}
|
|
|
|
/// <summary>合并表单参数,不包含 Query 的 ? 符号。</summary>
|
|
public static string MergeForm(IDictionary<string, string> form)
|
|
{
|
|
if (form == null) return "";
|
|
if (form.Count < 1) return "";
|
|
var cache = new List<string>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>合并表单参数,不包含 Query 的 ? 符号。</summary>
|
|
public static string MergeForm(TextSet form)
|
|
{
|
|
if (form == null) return "";
|
|
return MergeForm(form.GetOrigin());
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|