zoukankan      html  css  js  c++  java
  • HttpClient、WebClient、HttpWebRequest

    HttpClient

    HttpClient是.NET4.5提供的一个实现了http传输协议的类,该类可以说分装了HttpWebRequest和HttpWebResponse,它可以说是WebClient的精简升级版,适用于新的Metro-Style App以及原生的异步模式,在Metro-Style App中已不能使用原来的WebClient了,所以你可以把它看成是一个替代的类。它与WebClient相比,有几个特点:

    • 一个单一的HttpClient实例,便可以被并发地使用,而不需重新生成实例,换句话说,它是线程安全,而且一次生成,N次使用,中间就少了很多重复的设置过程。
    • HttpClient可以让你实现并插入自己的消息处理,这对于记录以及单元测试非常有好处。
    • HttpClient拥有丰富和扩展性强的Headers和Content类型系统。

    当然,HttpClient并非可以完全替代WebClient,因为后者还包括了处理FTP协议的能力,应该说HttpClient主要替代的是在Metro-Style App中WebClient实现Http协议的能力。

    下载文件

    using System.Net.Http;
    /// <summary>
    /// 可在整个生存期内实例化一次并重复使用
    /// 否则,大量实例化将耗尽重载下的socket套接字数
    /// </summary>
    static readonly HttpClient _httpClient = new HttpClient();
    
    /// <summary>
    /// 下载文件
    /// </summary>
    /// <param name="url">文件链接</param>
    /// <param name="localFile">可存储的本地文件路径</param>
    /// <returns></returns>
    public async Task DownloadAsync(string url, string localFile)
    {
        try
        {
            using (var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
            {
                response.EnsureSuccessStatusCode();
                using (var stream = await response.Content.ReadAsStreamAsync())
                {
                    using (var destiStream = File.Open(localFile, FileMode.Create))
                    {
                        await stream.CopyToAsync(destiStream);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
    

    验证大量链接(十万级)是否正常

    public async Task<bool> HttpClientGetStreamCheck(string url)
    {
        int size = 1000;
        _httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(0, size);
        var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
        using (var stream = await response.Content.ReadAsStreamAsync())
        {
            var bytes = new byte[size];
            var bytesread = stream.Read(bytes, 0, bytes.Length);
            if (bytesread != size)
            {
                return false;
            }
            stream.Close();
        }
        return true;
    }
    

    POST HTTP

    private async Task<string> PostInvoke<T>(string url, T body)
    {
        var rslt = string.Empty;
        string content = JsonSerializerEx.SerializeWithCamel(body);
        var stringContent = new StringContent(content);
        stringContent.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/json");
        // Headers参数可通过StringContent.Headers.Add()方法添加
        using (var response = await _httpClient.PostAsync(url, stringContent))
        {
            if (response.IsSuccessStatusCode && response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                rslt = await response.Content.ReadAsStringAsync();
            }
        }
        return rslt;
    }
    

    Send

    var request = GenerateRequestMessage(url, dto);
    await _httpClient.SendAsync(request, CancellationToken.None);
    
    
    private HttpRequestMessage GenerateRequestMessage(string url, object entity)
    {
        var request = new HttpRequestMessage(HttpMethod.Post, url);
        request.Headers.Accept.Clear();
        request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
        //request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
        string json = JsonSerializerEx.SerializeWithCamel(entity);
        request.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
    
        return request;
    }
    

    WebClient

    上传大文件比如说(300+M)的时候,WebClient将会报内存不足异常(Out of Memory Exceptions),究其原因是因为WebClient方式是一次性将整个文件全部读取到本地内存中,然后再以数据流形式发送至服务器。

    string url = @"http://forspeed.onlinedown.net/down/winrar-x64-580sc.exe";
    string localFile = "E:\winrar.exe";
    Download(url, localFile);
    
    /// <summary>
    /// Http方式下载文件
    /// </summary>
    /// <param name="url">http地址</param>
    /// <param name="localFile">本地文件</param>
    /// <returns></returns>
    static bool Download(string url, string localFile)
    {
        try
        {
            using (WebClient client = new WebClient())
            {
                client.Proxy = null;
                client.DownloadProgressChanged += Client_DownloadProgressChanged;
                client.DownloadFileCompleted += Client_DownloadFileCompleted;
                client.DownloadFileAsync(new Uri(url), localFile);
            }
        }
        catch (Exception ex)
        when (ex.GetType().Name == "WebException")
        {
            WebException we = (WebException)ex;
            using (HttpWebResponse hr = (HttpWebResponse)we.Response)
            {
                int statusCode = (int)hr.StatusCode;
                StringBuilder sb = new StringBuilder();
                StreamReader sr = new StreamReader(hr.GetResponseStream(), Encoding.UTF8);
                sb.Append(sr.ReadToEnd());
                Console.WriteLine(string.Format("下载出现异常!StatusCode:{0},Content: {1}", statusCode, sb));
            }
            return false;
        }
        return true;
    }
    private static void Client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            return;
        }
        Console.WriteLine("下载完成!");
    }
    private static void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
        long iTotalSize = e.TotalBytesToReceive;
        long iSize = e.BytesReceived;
        var percent = Convert.ToDouble(iSize) / Convert.ToDouble(iTotalSize) * 100;
        Console.WriteLine(string.Format("文件大小总共 {1} KB, 当前已接收 {0} KB;	({2}%)", (iSize / 1024), (iTotalSize / 1024), percent));
    }
    

    POST HTTP

    private async Task<string> PostInvoke<T>(string url, T body)
    {
        var rslt = string.Empty;
        using (var webClient = new WebClient())
        {
            // Headers参数可通过 webClient.Headers.Add()方法添加
            string content = JsonSerializerEx.Serialize(body);
            var data = Encoding.UTF8.GetBytes(content);
            data = await webClient.UploadDataTaskAsync(url, "POST", data);
            rslt = Encoding.UTF8.GetString(data);
        }
        return rslt;
    }
    

    HttpWebRequest

    下载到缓存

    /// <summary>
    /// 远程文件下载到内存流
    /// </summary>
    /// <param name="url">文件链接</param>
    /// <returns></returns>
    public async Task<Stream> GetStreamAsync(string url)
    {
        MemoryStream ms = new MemoryStream();
        try
        {
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            var arrByte = new byte[1024];
            int readCount;
            using (var stream = response.GetResponseStream())
            {
                while ((readCount = await stream.ReadAsync(arrByte, 0, arrByte.Length)) != 0)
                {
                    await ms.WriteAsync(arrByte, 0, readCount);
                }
            }
            ms.Seek(0, SeekOrigin.Begin);
        }
        catch (WebException e)
        {
            Console.WriteLine("This program is expected to throw WebException on successful run." +
                                                "
    
    Exception Message :" + e.Message);
            if (e.Status == WebExceptionStatus.ProtocolError)
            {
                Console.WriteLine("Status Code : {0}", ((HttpWebResponse)e.Response).StatusCode);
                Console.WriteLine("Status Description : {0}", ((HttpWebResponse)e.Response).StatusDescription);
                using (var reader = new StreamReader(e.Response.GetResponseStream()))
                {
                    string text = reader.ReadToEnd();
                    Console.WriteLine(text);
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }            
        return ms;
    }
    

    断点下载

    /// <summary>
    /// 断点下载
    /// </summary>
    /// <param name="url">文件链接</param>
    /// <param name="localFilePath">存放地址</param>
    /// <returns></returns>
    static bool Download(string url, string localFilePath)
    {
        bool flag;
        // 次下载的文件起始位置
        long startPosition;
        // 写入本地文件流对象
        FileStream fs;
        long remoteFileLength = GetHttpLength(url);
        Console.WriteLine("remoteFileLength: " + remoteFileLength);
        if (remoteFileLength == 745)
        {
            Console.WriteLine("远程文件不存在!");
            return false;
        }
        // 判断要下载的文件是否存在 断点续传
        if (File.Exists(localFilePath))
        {
            fs = File.OpenWrite(localFilePath);
            // 获取已经下载的长度
            startPosition = fs.Length;
            if (startPosition >= remoteFileLength)
            {
                Console.WriteLine($"获取已经下载的长度{startPosition}经大于等于远程文件长度{remoteFileLength}");
                fs.Close();
                return true;
            }
            else
            {
                fs.Seek(startPosition, SeekOrigin.Current);
            }
        }
        else
        {
            fs = new FileStream(localFilePath, FileMode.Create);
            startPosition = 0;
        }
        try
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            if (startPosition > 0)
            {
                // 定义远程文件读取位置
                request.AddRange((int)startPosition);
            }
            // 向服务器请求,获得服务器的回应数据流
            using (Stream stream = request.GetResponse().GetResponseStream())
            {
                byte[] arrByte = new byte[512];
                // 向远程文件读取字节流
                int contentSize = stream.Read(arrByte, 0, arrByte.Length);
                long curPos = startPosition;
                // 如果读取长度大于零则继续读
                while (contentSize > 0)
                {
                    curPos += contentSize;
                    int percent = (int)(curPos * 100 / remoteFileLength);
                    Console.WriteLine($"percent={percent}%");
                    // 写入本地文件
                    fs.Write(arrByte, 0, contentSize);
                    contentSize = stream.Read(arrByte, 0, arrByte.Length);
                }
            }
            flag = true;
    
        }
        catch (Exception ex)
        {
            string err = ex.Message;
            Console.WriteLine(err);
            flag = false;
        }
        finally
        {
            // 关闭流
            fs.Close();
        }
        return flag;
    }
    

    取得远程文件长度

    /// <summary>
    /// 取得远程文件长度
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    private static long GetHttpLength(string url)
    {
        long length = 0;
        try
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "HEAD";
            req.Timeout = 1000;
            using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
            {
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    length = response.ContentLength;
                }
            }
            return length;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        return length;
    }
    

    取得远程文件的名称

    /// <summary>
    /// 取得远程文件的名称
    /// </summary>
    /// <param name="url"></param>
    /// <returns></returns>
    private static string GetRemoteName(string url)
    {
        string rlst = string.Empty;
        try
        {
            var uri = new Uri(url);
            var req = (HttpWebRequest)WebRequest.CreateDefault(uri);
            req.Method = "HEAD";
            req.Timeout = 1000;
            using (var response = (HttpWebResponse)req.GetResponse())
            {
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    if (uri.Equals(response.ResponseUri))   // 未重定向
                    {
                        rlst = Path.GetFileName(url);
                    }
                    else
                    {
                        rlst = response.Headers["Content-Disposition"]; // attachment;filename=**
                        if (string.IsNullOrEmpty(rlst))
                        {
                            rlst = response.ResponseUri.Segments[response.ResponseUri.Segments.Length - 1];
                        }
                        else
                        {
                            rlst = rlst.Remove(0, rlst.IndexOf("filename=") + 9);
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        return rlst;
    }
    

    传递参数

    using System;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Text.Json;
    using System.Threading.Tasks;
    
    private async Task<TResponse> Post<TResponse, TRequest>(string webApiUrl, TRequest param, string contentType = "application/json") where TResponse : class where TRequest : class
    {
        try
        {
            string body = JsonSerializer.Serialize(param);   // 或 Newtonsoft.Json
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(webApiUrl);
            req.Method = "POST";
            req.ContentType = contentType;
            // params参数可直接跟在请求的URL地址后面
            // Headers参数可通过request.Headers.Add()方法添加
            var bytes = Encoding.UTF8.GetBytes(body);
            var stream = req.GetRequestStream();
            stream.Write(bytes, 0, bytes.Length);
            stream.Close();
            // 获取设置身份认证及请求超时时间
            req.Credentials = CredentialCache.DefaultCredentials;
            req.Timeout = 1_000;
            using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
            {
                var reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
                var rsltContent = await reader.ReadToEndAsync();
                return JsonSerializer.Deserialize<TResponse>(rsltContent);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        return null;
    }
    

    HTTP一般执行过程

    /// <summary>
    /// HTTP初始化,构造HttpWebRequest
    /// </summary>
    /// <typeparam name="T">body数据类型</typeparam>
    /// <param name="webApiUrl">资源URL</param>
    /// <param name="body">body数据</param>
    /// <param name="requestMethod">请求的方法(POST、DELETE、PUT、GET)</param>
    /// <param name="contentType">Content-Type类型</param>
    /// <param name="timeOut">超时时间(ms)</param>
    /// <returns></returns>
    private HttpWebRequest CreateWebRequest<T>(string webApiUrl, T body, string requestMethod, string contentType = "application/json", int timeOut = 10_000) where T : class
    {
        var rslt = CreateWebRequest(webApiUrl, requestMethod, contentType, timeOut);
        if (requestMethod.ToUpper() == "GET")
        {
            return rslt;
        }
        string content = JsonSerializerEx.Serialize(body);
        var bytes = Encoding.UTF8.GetBytes(content);
        var stream = rslt.GetRequestStream();
        stream.Write(bytes, 0, bytes.Length);
        stream.Close();
        return rslt;
    }
    /// <summary>
    /// HTTP初始化,构造HttpWebRequest
    /// </summary>
    /// <param name="webApiUrl">资源URL</param>
    /// <param name="requestMethod">请求的方法(POST、DELETE、PUT、GET)</param>
    /// <param name="contentType">Content-Type类型</param>
    /// <param name="timeOut">超时时间(ms)</param>
    /// <returns></returns>
    private HttpWebRequest CreateWebRequest(string webApiUrl, string requestMethod = "GET", string contentType = "application/json", int timeOut = 10_000)
    {
        var rslt = (HttpWebRequest)WebRequest.Create(webApiUrl);
        rslt.Method = requestMethod;
        rslt.ContentType = contentType;
        rslt.KeepAlive = true;
        // params参数可直接跟在请求的URL地址后面
        // Headers参数可通过request.Headers.Add()方法添加 token等
        // 获取设置身份认证及请求超时时间
        rslt.Credentials = CredentialCache.DefaultCredentials;
        rslt.Timeout = timeOut;
    
        return rslt;
    }
    
    /// <summary>
    /// 执行HTTP,返回响应内容
    /// </summary>
    /// <param name="request"></param>
    /// <returns></returns>
    private async Task<string> InvokeWebRequestAsync(HttpWebRequest request)
    {
        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            var reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
            return await reader.ReadToEndAsync();
        }
    }
    

    HTTP 签名+认证

    1. 确保来源准确、中间无篡改,对参数进行签名,并在参数后面追加一个签名字段;

    形如:http://xxx/api/third/method?para1=value1&para2=value2&sign=signValue

    1. 确保当前调用有权限,可以把token、用户名与密码也加到签名里,具体的签名规则可以双方协定,
      比如对所有待签名参数可以按参数名称递增排序;签名使用SHA1;签名结果大写表示;
    2. 用户名与密码也可以加到Headers里面;
    private async Task<XXXRepsonseDto> MethodAsync(string url, string token, XXXRequestDto dtoRequest, DtoConfig config)
    {
        var innerAccess = new InnerAccessAuthorization();
        innerAccess.App_id = config.App_id;
        innerAccess.Sercret = config.Sercret;
        
        // 签名
        var dicInnerAccess = innerAccess.ToDicDescValue();
        var uploadDataDto = new UploadDataRequestDto
        {
            Token = token,
            Timestamp = WSCommFunc.GenerateTimestamp(),
        };
        var dic = uploadDataDto.ToDicDescValue();
        var pairs = WSCommFunc.GenerateParam(dic, dicInnerAccess, config);
        url = WSCommFunc.ConcatUrl(url, pairs.Value, pairs.Key);
    
        // 接口调用
        _logger.LogInformation($"MethodAsync request:{JsonSerializer.Serialize(dtoRequest)}");
        var api = new HttpHelper();
        var request = api.CreateWebRequest(url, dtoRequest, "POST");
        request.Headers.Add("Inner-Access-Authorization", JsonSerializerEx.SerializeWithCamel(innerAccess));
        var content = await api.InvokeWebRequestAsync(request);
        _logger.LogInformation($"MethodAsync response:{content}");
        var dto = JsonSerializerEx.DeserializeWithCamel<HandlerResult<UploadDataRepsonseDto>>(content);
        // 验证结果
        if(dto.State == "10000")
        {
            _logger.LogInformation($"MethodAsync 返回正常--{JsonSerializer.Serialize(dto)},request:{JsonSerializer.Serialize(dtoRequest)}");
        }
        else
        {
            _logger.LogError($"MethodAsync 返回报错--{JsonSerializer.Serialize(dto)},url:{url},request:{JsonSerializer.Serialize(dtoRequest)}");
            return null;
        }
        return dto.Value;
    
    }
    
    /// <summary>
    /// 内部访问授权类
    /// </summary>
    internal class InnerAccessAuthorization
    {
        /// <summary>
        /// 第三方平台身份
        /// </summary>
        [Description("app_id")]
        public string App_id { get; set; } 
        /// <summary>
        /// 版本
        /// </summary>
        [Description("version")]
        public string Version { get; set; } = "1.0";
        /// <summary>
        /// 接口签名安全认证
        /// </summary>
        public string Sercret = ""; 
    }
    
  • 相关阅读:
    VBA.replace替换单引号或双引号
    读取文件
    UPDATE
    alter update
    SQL日期格式
    python map的用法
    python os模块用法
    python re.I compile search
    python 正则匹配
    通过list中值得名称查询索引号
  • 原文地址:https://www.cnblogs.com/wesson2019-blog/p/12127201.html
Copyright © 2011-2022 走看看