C# abp框架Http辅助类

一、定义接口

为什么要定义接口而不直接使用静态类,因为接口可以注入缓存对象,这样就能从缓存中读取指定的请求头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
 
namespace Test.Common
{
    /// <summary>
    /// HTTP请求接口
    /// </summary>
    public interface IHttpClientUtils: IApplicationService
    {
        /// <summary>
        /// 发送HTTP请求,注意Get请求第4个参数才是缓存key,因此第3个参数请设置为null
        /// </summary>
        /// <param name="method">HttpMethod.Get、HttpMethod.Post、HttpMethod.Put、HttpMethod.Delete</param>
        /// <param name="url">完整地址</param>
        /// <param name="model">请求体对象</param>
        /// <param name="cacheKey">鉴权请求头缓存key</param>
        /// <param name="headers">请求头</param>
        /// <returns></returns>
        Task<T> SendAsync<T>(HttpMethod method, string url, object model = null, string cacheKey = "", Dictionary<string, string> headers = null);
 
 
        /// <summary>
        /// 发送HTTP表单请求
        /// </summary>
        /// <param name="method">HttpMethod.Post、HttpMethod.Put</param>
        /// <param name="url">完整地址</param>
        /// <param name="model">请求体对象</param>
        /// <param name="cacheKey">鉴权请求头缓存key</param>
        /// <param name="headers">请求头</param>
        /// <returns></returns>
        Task<T> SendFormAsync<T>(HttpMethod method, string url, object model, string cacheKey = "", Dictionary<string, string> headers = null);
    }
}

二、实现接口

1、支持https

2、支持从缓存读取鉴权请求头

3、支持对象携带文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Caching;
using Volo.Abp.Json;
 
namespace Test.Common
{
    /// <summary>
    /// HTTP请求实现
    /// </summary>
    public class HttpClientUtils: ApplicationService, IHttpClientUtils
    {
        private readonly ILogger<HttpClientUtils> _logger;
        private readonly IDistributedCache<Dictionary<string, string>> _cache;
        public HttpClientUtils(
            ILogger<HttpClientUtils> logger,
            IDistributedCache<Dictionary<string, string>> cache)
        {
            _cache = cache;
            _logger = logger;
        }
 
        /// <summary>
        /// 发送HTTP请求
        /// </summary>
        /// <param name="method">HttpMethod.Get、HttpMethod.Post、HttpMethod.Put、HttpMethod.Delete</param>
        /// <param name="url">完整地址</param>
        /// <param name="model">请求体对象</param>
        /// <param name="cacheKey">鉴权请求头缓存key</param>
        /// <param name="headers">请求头</param>
        /// <returns></returns>
        public async Task<T> SendAsync<T>(HttpMethod method, string url, object model = null, string cacheKey = "", Dictionary<string, string> headers = null)
        {
            _logger.LogInformation($"SendAsync {method.Method} url = {url}, cacheKey = {cacheKey}, data = {JsonConvert.SerializeObject(model)}");
            try
            {
                using (var client = new HttpClient())
                {
                    if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
                    {
                        url = $"http://{url}";
                    }
                    using (var request = new HttpRequestMessage(method, url))   
                    {
                        if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
                        {
                            SetHttps();
                        }
 
                        var sc = new StreamContent(new MemoryStream());
                        if (model != null)
                        {
                            var jsonStr = JsonConvert.SerializeObject(model);
                            var bytes = Encoding.UTF8.GetBytes(jsonStr);
                            sc = new StreamContent(new MemoryStream(bytes));
                        }
 
                        HeaderHandler(request, sc.Headers, "application/json", headers, cacheKey);
 
                        request.Content = sc;
                        var rsp = await client.SendAsync(request);
                        return ResponseHandler<T>(rsp);
                    }
                }
            }
            catch (Exception e)
            {
                _logger.LogError($"SendAsync {method.Method} 请求:{url}, 错误消息:{e.Message}, 请求参数:{JsonConvert.SerializeObject(model)}");
                throw new Exception($"请求HTTP服务{new Uri(url).AbsolutePath}异常:{e.Message}");
            }
        }
 
         
        /// <summary>
        /// 发送HTTP表单请求
        /// </summary>
        /// <param name="method">HttpMethod.Post、HttpMethod.Put</param>
        /// <param name="url">完整地址</param>
        /// <param name="model">请求体对象</param>
        /// <param name="cacheKey">鉴权请求头缓存key</param>
        /// <param name="headers">请求头</param>
        /// <returns></returns>
        public async Task<T> SendFormAsync<T>(HttpMethod method, string url, object model, string cacheKey = "", Dictionary<string, string> headers = null)
        {
            _logger.LogInformation($"SendFormAsync {method.Method} url = {url}, cacheKey = {cacheKey}, data = {JsonConvert.SerializeObject(model)}");
            try
            {
                var client = new RestClient();
                var request = new RestRequest(url, Method.POST);
                if (method == HttpMethod.Put)
                {
                    request.Method = Method.PUT;
                }
                 
                SetHeader(request, cacheKey, headers);
                request.AlwaysMultipartFormData = true;
 
                var props = model.GetType().GetProperties();
                foreach (var item in props)
                {
                    var k = item.Name;
                    var v = item.GetValue(model);
 
                    if (v == null)
                    {
                        _logger.LogInformation($"SendFormAsync {method.Method} url = {url} {k} value is null");
                        continue;
                    }
 
                    if (v.GetType().Name == "Dictionary`2") // 文件只支持字典类型
                    {
                        if (v is Dictionary<string, byte[]>)
                        {
                            var files = v as Dictionary<string, byte[]>;
                            foreach (var obj in files) // 多个文件只能一个个添加,不能集合到List中
                            {
                                string ext = Path.GetExtension(obj.Key);
                                ext = ext.RemovePreFix(".");
 
                                _logger.LogInformation($"SendFormAsync file key = {k}, name = {obj.Key}, ext = {ext}, size = {obj.Value.Length}");
 
                                request.AddFileBytes(k, obj.Value, obj.Key, $"image/{ext}");
                            }
                        }
                    }
                    else
                    {
                        request.AddParameter(k, v);
                    }
                }
 
                var response = await client.ExecuteAsync(request);
                if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
                {
                    _logger.LogError($"SendFormAsync {method.Method} {url} 响应失败: {response.Content}");
                    throw new Exception($"服务器响应失败:status = {response.StatusCode}");
                }
 
                _logger.LogInformation($"SendFormAsync {method.Method} {url} Response: {response.Content}");
 
                return JsonConvert.DeserializeObject<T>(response.Content, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
            }
            catch (Exception e)
            {
                _logger.LogError($"SendFormAsync {method.Method} 请求:{url}, 错误消息:{e.Message}, 请求参数:{JsonConvert.SerializeObject(model)}");
                throw new Exception($"请求HTTP服务{new Uri(url).AbsolutePath}异常:{e.Message}");
            }
        }
 
        /// <summary>
        /// 设置请求头
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cacheKey"></param>
        private void SetHeader(RestRequest request, string cacheKey, Dictionary<string, string> headers)
        {
            // 鉴权请求头
            var cacheHeaders = _cache.Get(cacheKey);
            if (cacheHeaders != null)
            {
                foreach (var dic in cacheHeaders)
                {
                    _logger.LogInformation($"SetHeader cache header: {dic.Key} = {dic.Value}");
 
                    if (dic.Key.Equals("authorization", StringComparison.OrdinalIgnoreCase))
                    {
                        request.AddHeader("Authorization", dic.Value);
                    }
                    else
                    {
                        request.AddHeader(dic.Key, dic.Value);
                    }
                }
            }
 
            // 默认请求头
            if (headers != null && headers.Count > 0)
            {
                foreach (var dic in headers)
                {
                    request.AddHeader(dic.Key, dic.Value);
                }
            }
        }
 
        /// <summary>
        /// 处理请求头
        /// </summary>
        /// <param name="request">new HttpRequestMessage</param>
        /// <param name="contentHeader">new MultipartFormDataContent</param>
        /// <param name="contentType">multipart/form-data</param>
        /// <param name="headers">Dictionary<string, string></param>
        /// <param name="cacheKey">authorization</param>
        private void HeaderHandler(HttpRequestMessage request, HttpContentHeaders contentHeader, string contentType, Dictionary<string, string> headers, string cacheKey)
        {
            // 添加默认请求头
            if (headers != null && headers.ContainsKey("Content-Type"))
            {
                contentType = headers["Content-Type"];
            }
 
            if (string.IsNullOrEmpty(contentType))
            {
                contentType = "application/json"; // 默认application/json
            }
 
            _logger.LogInformation($"HeaderHandler {request.Method.Method} {request.RequestUri.AbsolutePath} default contentType header: {contentType}");
 
            if (contentType.IndexOf("multipart/form-data") != -1)
            {
                contentHeader.Remove("Content-Type"); // MediaTypeHeaderValue不支持解析boundary,所以先删除再Add
                contentHeader.TryAddWithoutValidation("Content-Type", contentType);
            }
            else
            {
                contentHeader.ContentType = new MediaTypeHeaderValue(contentType);
            }
 
            // 添加鉴权请求头
            var cacheHeaders = _cache.Get(cacheKey);
            if (cacheHeaders != null)
            {
                foreach (var dic in cacheHeaders)
                {
                    _logger.LogInformation($"HeaderHandler {request.Method.Method} {request.RequestUri.AbsolutePath} cache header: {dic.Key} = {dic.Value}");
 
                    if (dic.Key.Equals("authorization", StringComparison.OrdinalIgnoreCase))
                    {
                        request.Headers.Authorization = new AuthenticationHeaderValue(dic.Value);
                        // request.SetBearerToken(dic.Value); // 这个方法里加了前缀Bearer,针对接口不同
                    }
                    else
                    {
                        contentHeader.Add(dic.Key, dic.Value); // cookie、token ...
                    }
                }
            }
 
            // 添加参数请求头
            if (headers != null && headers.Count > 0)
            {
                foreach (var dic in headers)
                {
                    if (contentHeader.ContentType.MediaType == dic.Value)
                    {
                        continue;
                    }
                    contentHeader.Add(dic.Key, dic.Value);
                }
            }
        }
 
        /// <summary>
        /// 处理HTTP响应
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="rsp"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private T ResponseHandler<T>(HttpResponseMessage rsp)
        {
            if (rsp.StatusCode != HttpStatusCode.OK && rsp.StatusCode != HttpStatusCode.Created)
            {
                _logger.LogError($"ResponseHandler {rsp.RequestMessage.Method.Method} {rsp.RequestMessage.RequestUri.AbsoluteUri} 响应失败: {rsp.Content.ReadAsStringAsync().Result}");
                throw new Exception($"服务器响应失败:status = {rsp.StatusCode}");
            }
 
            if (rsp is T)
            {
               return (T)(object)rsp;  // 返回HttpResponseMessage,用于获取响应头
            }
            else
            {
                var json = rsp.Content.ReadAsStringAsync().Result;
                _logger.LogInformation($"ResponseHandler {rsp.RequestMessage.Method.Method} {rsp.RequestMessage.RequestUri.AbsoluteUri} 响应:{json}");
                return JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
                // return rsp.Content.ReadFromJsonAsync<T>().Result; // 会检查int null类型报错
            }
        }
 
        /// <summary>
        /// 设置HTTPS
        /// </summary>
        private void SetHttps()
        {
            // set remote certificate Validation auto pass
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(RemoteCertificateValidate);
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
            // FIX:修复不同.Net版对一些SecurityProtocolType枚举支持情况不一致导致编译失败等问题,这里统一使用数值
            //ServicePointManager.SecurityProtocol = (SecurityProtocolType)48 | (SecurityProtocolType)3072 | (SecurityProtocolType)768 | (SecurityProtocolType)192;
        }
 
        /// <summary>
        /// 远程证书验证
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="cert"></param>
        /// <param name="chain"></param>
        /// <param name="error"></param>
        /// <returns>验证是否通过,始终通过</returns>
        private bool RemoteCertificateValidate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error)
        {
            return true;
        }
    }
}

三、遇到问题

1、无法添加请求头content-type

var s = new HttpRequestMessage()
s.Headers.Add("Content-Type", "")
报错:Misused header name, 'Content-Type'. Make sure request headers are used with HttpRequestMessage,

var b = new HttpContentHeader()
b.Add("Content-Type", "")
报错:Cannot add value because header 'Content-Type' does not support multiple values.

var b = new HttpContentHeader()
b.ContentType = new MediaTypeHeaderValue("multipart/form-data; boundary=----8db27a5c21ea4f8");
报错:The format of value 'multipart/form-data; boundary=----8db27a5c21ea4f8' is invalid
报错:'multipart/form-data; ----8db27a507065123' is invalid
报错:'multipart/form-data; boundary=--8db27ca4cfafb57' is invalid

1、解决方案:

1
请看方法:HeaderHandler

 

posted @   codeIsArt  阅读(422)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示