02 发送HTTP请求

发布时间 2024-01-05 14:36:12作者: 讨厌敲代码的老郭

02 C#发送HTTP请求

HttpWebRequest

HttpWebRequest提供 WebRequest 类的特定于 HTTP 的实现。用于发送网络请求,使用方法如下

private string HttpGet(string api)
{
  // 请求路径
  string serviceAddress = api;
  // 创建请求
  HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceAddress);
  // 设置请求方法
  request.Method = "GET";
  // 设置网络超时时间
  request.Timeout = 10000;
  // 获取服务器响应
  HttpWebResponse response = (HttpWebResponse)request.GetResponse();
  // 读取响应数据
  Stream myResponseStream = response.GetResponseStream();
  StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8);
  //返回json字符串
  string result = myStreamReader.ReadToEnd();
  // 关闭文件流
  myStreamReader.Close();
  myResponseStream.Close();
  return result;
}

如果发送POST请求并携带参数,需要设置请求体数据类型,并且将数据写入请求体,添加以下代码即可

注意:必须在设置了Method属性之后才能将数据添加到请求体,否则代码会报错

// 设置请求内容类型
request.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
// 请求体数据
string data = "username=张三&password=123456";
// 字符串转换为字节码
byte[] buffer = Encoding.UTF8.GetBytes(data);
// 参数数据长度
request.ContentLength = buffer.Length;
// 将参数写入请求地址中
request.GetRequestStream().Write(buffer, 0, buffer.Length);

请求封装

上面写的代码将会在主线程执行,会阻塞线程,可以将他封装一下,使用Task执行

 public Task<string> MyHttp(string uri,string method="GET",string data="")
 {
     // 建议在子线程中调用,避免阻塞引起程序假死:
     return Task.Run(() => {
         // 创建请求
         HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
         // 设置请求方法(默认:GET)
         request.Method = method;
         // 设置请求内容类型
         request.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
         // 设置网络超时时间(毫秒)
         request.Timeout = 30000;
         // 处理POST请求的参数问题
         if (method.ToUpper() == "POST")
         {
             // 字符串转换为字节码
             byte[] buffer = Encoding.UTF8.GetBytes(data);
             // 参数数据长度
             request.ContentLength = buffer.Length;
             // 将参数写入请求地址中
             request.GetRequestStream().Write(buffer, 0, buffer.Length);
         }
         // 获取服务器响应
         HttpWebResponse response = (HttpWebResponse)request.GetResponse();
         // 获取响应数据流
         Stream stream = response.GetResponseStream();
         // 创建流读取器
         StreamReader reader = new StreamReader(stream, Encoding.UTF8);
         // 读取数据
         string resStr = reader.ReadToEnd();
         // 关闭读取器
         reader.Close();
         // 关闭数据流
         stream.Close();
         // 控制台打印
         // Console.WriteLine(resStr);
         return resStr;
     });
 }

使用该方法的方式如下

private async void button1_Click(object sender, EventArgs e)
{
    string uri = "http://www.baidu.com";
    richTextBox1.Text = await MyHttp(uri);
}

HttpClient

HttpClient 类提供一个类,用于从 URI 标识的资源发送 HTTP 请求和接收 HTTP 响应。微软不建议用于 HttpWebRequest​ 新开发。 请改用 System.Net.Http.HttpClient 类。

它的用法更加的简单方便

async void Fn(){
  // 1、创建一个新的HttpClient
  HttpClient client = new HttpClient();
  // 2、GetAsync()方法:以【异步操作】将 【GET请求】发送到指定的 Uri。
  // await运算符:会暂停对异步方法的求值,直到异步操作完成为止。 异步操作完成后,await操作符将返回操作结果(如果有)。
  HttpResponseMessage message = await client.GetAsync(uri);
  // 3、HttpResponseMessage.Content.ReadAsStringAsync()获取响应的数据
  textBox1.Text = "响应数据:" + await message.Content.ReadAsStringAsync();
}

HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。推荐的使用方式如下

public class HttpController
{
    private static readonly HttpClient httpClient;

    static HttpController()
    {
        httpClient = new HttpClient(socketsHandler);
    }
}

常用属性/方法

  • HttpClient.GetAsunc(uri)​ 以【异步操作】将 【GET请求】发送到指定的 Uri
  • HttpResponseMessage.Content.ReadAsStringAsync()​ ​​获取响应的数据
  • HttpResponseMessage.StatusCode​ 响应状态码
  • HttpResponseMessage.Headers​ 响应头

其他请求方式

常用的请求方式有:get、post、put、delete,分别对应查、增、改、删操作类型,使用方式和GetAsync​基本相同的其他几个方法

client.GetAsync(uri)、client.PostAsync(uri)、client.PutAsync(uri)、client.DeleteAsync(uri)​,这几个方法都是对常用请求方法的封装,他们的完整写法如下

HttpClient client = new HttpClient();  // 初始化对象
// 创建请求信息,指定请求类型和请求地址(此例指定请求类型为Head,其他的Get、Post等均可以这么写)
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Head, "http://127.0.0.1:3001/laoguo-head");
// 发送请求
var resMessage = await client.SendAsync(req);

携带请求体

// 1、指定数据, 编码格式, ContentType
// application/x-www-form-urlencoded
StringContent content = new StringContent(p.ToString(), Encoding.UTF8, "application/x-www-form-urlencoded");

// application/json
StringContent content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json");

// 2、发送请求,并指定uri和请求体
client.PostAsync(uri, content);

也可以使用一些类,已经内置了设置编码格式和请求体内容的代码

// application/x-www-form-urlencoded
FormUrlEncodedContent content = new FormUrlEncodedContent(new Dictionary<string,string>()
{
    {"name","张三" },
    {"age","12" }
});

// application/json
JsonContent content = JsonContent.Create(obj);

设置请求头

当使用HttpClient​时有两种方式添加请求头:

  1. 使用HttpClient.DefaultRequestHeaders​为所有的请求添加请求头
  2. 受用HttpRequestMessage.Headers​为单个请求添加请求头

所有请求添加,添加后所有使用该实例的请求都会携带该请求头

HttpClient.DefaultRequestHeaders.Add("ApiKey", key);

单个请求添加

var request = new HttpRequestMessage(HttpMethod.Get, randomNumberUrl);  // 创建请求对象
req.Headers.Add("a", "1");  // 添加请求头
await client.SendAsync(req);  // 发送请求

文件的下载

普通下载

资源的下载一般是请求回来一个字节数组,然后使用IO操作将字节数组写入本地,示例代码如下

string filename = "abc.webp";   // 目标文件名称,尽量和源文件后缀一致
string uri = "https://cn.bing.com/th?id=OHR.AlpsCastles_ZH-CN5078013932_1920x1080.webp&qlt=50";
HttpClient client = new HttpClient();
// 将GET请求发送到指定URI并在异步操作中以字节数组的形式返回响应正文。
byte[] buffer = client.GetByteArrayAsync(uri).Result;
// 我们用GetFolderPath()方法确定系统文件夹
// MyPictures:图片  MyDocuments:文档 MyMusic:音乐 Desktop: 桌面.....
string documentsPath = System.Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
// 文件的写入的本地路径
// Path.Combine:合并两个路径(不用加 "/")
string localFilePath = Path.Combine(documentsPath, filename);
// 写入本地
File.WriteAllBytes(localFilePath, buffer);

另一种写法,工作原理相同

 // 1.创建HttpClient对象。
 HttpClient httpClient = new HttpClient();
 // 2.使用GetAsync方法下载文件,并获取响应对象。
 HttpResponseMessage response = await httpClient.GetAsync("http://192.168.109.3/%E8%BD%AF%E4%BB%B6/VisualStudioSetup.exe");
 // 3.使用GetFileNameFromHttpResponse方法获取文件名。
 string fileName = "VisualStudioSetup.exe";
 // 4.将响应内容读取到Stream对象中。
 Stream stream = await response.Content.ReadAsStreamAsync();
 // 5.使用FileStream对象将Stream写入文件。
 using (FileStream fileStream = new FileStream(fileName, FileMode.Create))
 {
     stream.CopyTo(fileStream);
 }

带进度的下载

当我们下载的文件较大时,给用户显示一个进度条是更好的选择,以下是带有进度的下载操作

 // 1.创建HttpClient对象。
 HttpClient httpClient = new HttpClient();
 // 2.使用GetAsync方法下载文件,并获取响应对象。
 // 参数2用于指定该请求应该等待所有内容都响应后才完成还是得到响应头后就完成
 HttpResponseMessage response = await httpClient.GetAsync("http://192.168.109.3/%E6%96%B0%E5%BB%BA%E6%96%87%E4%BB%B6%E5%A4%B9/phpstudy_x64_8.1.1.3.exe", HttpCompletionOption.ResponseHeadersRead);
 // 3.使用GetFileNameFromHttpResponse方法获取文件名。
 string fileName = "VisualStudioSetup.exe";
 // 4.将响应内容读取到Stream对象中。
 Stream stream = await response.Content.ReadAsStreamAsync();
 // 5. 记录响应体大小
 long all = response.Content.Headers.ContentLength ?? 0;
 // 记录已经下载的字节大小
 long downloadByte = 0;
 // 6. 创建文件流准备本地写入
 using (FileStream fs = new FileStream(fileName, FileMode.Create))
 {
     byte[] bytes = new byte[1024 * 8];  // 准备一个容器
     int nowReadCount = 0;   // 记录本次读取到的数据长度
     while ((nowReadCount = await stream.ReadAsync(bytes, 0, bytes.Length)) > 0)
      {
         downloadByte += nowReadCount;   // 记录已经下载的资源大小
         fs.Write(bytes, 0, nowReadCount);   // 写入本地
         progressBar1.Value = (int)((double)downloadByte / all * 100);   // 更新进度
     }
 }

带进度下载的另一种实现

private async void button8_ClickAsync(object sender, EventArgs e)
{
    // 1. 处理上传或下载进度事件
    HttpClientHandler handler = new HttpClientHandler();
    ProgressMessageHandler progressMessageHandler = new ProgressMessageHandler(handler);
    progressMessageHandler.HttpReceiveProgress += httpSendProgress; // 监听下载事件

    // 2. 创建HttpClient对象,并指定进度通知对象
    HttpClient httpClient = new HttpClient(progressMessageHandler);
    // 3. 发送请求,开始下载
    var res = await httpClient.GetAsync(@"https://nodejs.org/dist/v20.10.0/node-v20.10.0-x64.msi");
    // 4. 读取响应内容并写入本地
    var body = await res.Content.ReadAsStreamAsync();
    var fs = new FileStream("nodejs.msi", FileMode.Create);
    body.CopyTo(fs);
    // 5. 关闭流
    fs.Close();
    body.Close(); 

}
private void httpSendProgress(object sender, HttpProgressEventArgs e)
{
    // e.ProgressPercentage     记录当前上传的进度
    Console.WriteLine(e.ProgressPercentage);
    this.Invoke((Action)(() =>
    {
        this.progressBar1.Value = e.ProgressPercentage;
    }));
}

文件的上传

请求数据的封装

文件上传必须使用POST请求,因为要携带大量的请求内容,而且文件上传必须使用multipart/form-data​​类型的请求体,和之前的a=1&b=2​​、{"a":1,"b":2}​​的写法不太相同。

// 实例化multipart表单模型
MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();

FileStream fileStream = new FileStream("./Upload.exe", FileMode.Open, FileAccess.Read);

// 方案1. 使用StreamContent添加文件流
multipartFormDataContent.Add(new StreamContent(fileStream), "服务器要求的字段名", "图片的名字");
// 方案2. 使用ByteArrayContent添加字节数组
byte[] buffer = new byte[fileStream.Length];
fs.Read(buffer, 0, buffer.Length);
multipartFormDataContent.Add(new ByteArrayContent(buffer), "服务器要求的字段名", "图片的名字");

// 如果是普通的字符,需要使用StringContent
multipartFormDataContent.Add(new StringContent("张三"), "name");

文件上传

// 1.创建HttpClient对象。
HttpClient httpClient = new HttpClient();
// 2. 创建文件读取流
string path = @"C:\Users\g8496\Pictures\wallhaven-jxvw7q.png";
var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
// 3. 创建formData对象,并写入参数
var formData = new MultipartFormDataContent
{
    { new StreamContent(fs), "ffff", "wallhaven-jxvw7q.png" },  // 文件数据
    { new StringContent("张三"), "name" }                        // 其他参数
};
// 4. 发送请求,开始上传
var res = await httpClient.PostAsync("http://127.0.0.1:3001/file", formData);
// 5. 读取响应内容
string body = await res.Content.ReadAsStringAsync();
MessageBox.Show(body);

带进度的上传

private async void button8_ClickAsync(object sender, EventArgs e)
{
    // 处理上传进度事件
    HttpClientHandler handler = new HttpClientHandler();
    ProgressMessageHandler progressMessageHandler = new ProgressMessageHandler(handler);
    progressMessageHandler.HttpSendProgress += httpSendProgress;    // 添加事件监听

    // 1.创建HttpClient对象,并指定进度通知对象
    HttpClient httpClient = new HttpClient(progressMessageHandler);
    // 2. 创建文件读取流
    string path = @"C:\Users\g8496\Downloads\Compressed\Clash.for.Windows-0.20.24-win.7z";
    var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
    // 3. 创建formData对象,并写入参数
    var formData = new MultipartFormDataContent
    {
        { new StreamContent(fs), "ffff", "Clash.for.Windows-0.20.24-win.7z" }
    };

    // 4. 发送请求,开始上传
    var res = await httpClient.PostAsync("http://127.0.0.1:3001/file", formData);
    // 5. 读取响应内容
    string body = await res.Content.ReadAsStringAsync();
    MessageBox.Show(body);
}
private void httpSendProgress(object sender, HttpProgressEventArgs e)
{
    // e.ProgressPercentage     记录当前上传的进度
    Console.WriteLine(e.ProgressPercentage);
    this.Invoke((Action)(() =>
    {
        this.progressBar1.Value = e.ProgressPercentage;
    }));
}

MyHttp请求辅助类的封装