C/S客户端程序 winform接收外部http (GET|POST)请求 工具类逻辑开发
前言
我们知道web项目(即B/S端程序的S端)是很容易提供API接口,供外部进行访问的,这是Web本身的特性所然。Web项目在发布后,会挂载到比如IIS管理器,上面会要求配置IP和端口号,外部访问时根据约定的IP,端口,以及约定的路由路径、请求方式、传参等就很容易外部对内API接口访问。
客户端程序(即C/S端程序的C端)对外提供API接口则不那么容易,毕竟本身客户端没有明确需要表面自身身份的IP和端口,发布好的C/S端程序拷到哪台机器都可以使用!
问题
现在有特殊场景需求,我们开发的winform (C/S架构)程序需要和上层的总控系统对接,而总控系统可能是B/S架构,它需要和我的winform程序进行接口对接,需要我暴露自身的接口,由它来调用。
这就面临一个问题:winform如何能接收外部http (GET|POST)请求?
解决过程
1.我们现在原有的项目中,新建一个HttpServerHelper类,内部封装一个Http服务器逻辑,将winform程序当作一个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 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 | using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace DetectMonitor.Helper { public class HttpServerHelper { private readonly string selfUrl; //客户端自身(也就是该程序本身)作为服务器,需要确认以自身哪个端口来对外监听外部的访问 private readonly HttpListener myListener; private readonly Dictionary< string , Func<HttpListenerContext, Task< string >>> myRoutes; //路由映射 //构造函数 //selfListenAddrs:实际给一个 http://ip:port/就行,不用搞太多端口区分 public HttpServerHelper( string [] selfListenAddrs) { if (!HttpListener.IsSupported) { throw new NotSupportedException( "当前平台不支持HttpListener" ); } myListener = new HttpListener(); //初始化HttpListener实例 //初始化路由映射字典 myRoutes = new Dictionary< string , Func<HttpListenerContext, Task< string >>>(); // //为服务器(就是自身)添加地址和端口 (实际) foreach ( string addr in selfListenAddrs) { myListener.Prefixes.Add(addr); //内容格式http://ip:port/api/ } selfUrl = selfListenAddrs[0]; //记录第一个监听的地址 } //判断监听实例是否在监听 public bool IsOpen { get { return myListener.IsListening; } } //启动服务器 public void Start() { myListener.Start(); myListener.BeginGetContext(ProcessRequestCallback,myListener); //处理客户端请求 } //停止服务器 public void Stop() { myListener.Stop(); myListener.Close(); } //添加路由和处理程序的映射关系 public void AddRoute( string route, Func<HttpListenerContext,Task< string >> handler) { myRoutes.Add(route, handler); } //处理客户端(即外部程序)发来请求的处理逻辑 (这里是定义的回调函数) private async void ProcessRequestCallback(IAsyncResult result) { HttpListener listener = (HttpListener)result.AsyncState; //开始下一个请求的监听 listener.BeginGetContext(ProcessRequestCallback,listener); try { HttpListenerContext context = listener.EndGetContext(result); //获取请求方法和URL路径 string httpMethod = context.Request.HttpMethod; string url = context.Request.Url.AbsolutePath; string responseString = "No Data!" ; //默认响应字符串 Func<HttpListenerContext, Task< string >> handler; //如果请求路径存在与路由映射中,执行相应的处理程序 if (myRoutes.TryGetValue(url, out handler)) { //获取处理程序返回的响应数据 responseString = await handler(context); //将响应数据编码成字节数组 byte [] buffer = System.Text.Encoding.UTF8.GetBytes(responseString); //设置响应的内容长度和状态码 context.Response.ContentLength64 = buffer.Length; context.Response.StatusCode = ( int )HttpStatusCode.OK; // 设置响应头中的 Content-Type 为 application/json 并指定字符编码为 UTF-8(修复浏览器访问GET请求时,反馈值中文乱码问题) context.Response.ContentType = "application/json; charset=utf-8" ; //将响应写入输出流并关闭输出流 context.Response.OutputStream.Write(buffer, 0, buffer.Length); context.Response.OutputStream.Close(); } else { //如果请求不存在与路由映射中,返回404错误 context.Response.StatusCode = ( int )HttpStatusCode.NotFound; context.Response.Close(); } } catch (Exception ex) { Log4NetHelper.Error( "HttpServerHelper.ProcessRequestCallback:" + ex.Message); } } } } |
2. 编写HttpServer的监听并调用对应接口的逻辑,在项目的加载函数或初始化函数调用执行,这样实时监听外部的端口访问,一旦监听到,就分析访问的路由,调用对应路由映射的函数
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 | //20250102 本机C/S客户端程序提供开放接口需求,供外部调用 public static void IniHttpServer() { //注意:参数内的IP就是本机以后对外身份的ip(正式使用需要修改), 参数内的端口就是本机对外接受“接口”访问的端口 HttpServerHelper httpServer = new HttpServerHelper( new string [] { "http://127.0.0.1:7777/" }); //绑定映射,处理函数 (路径就是将来给到外部来访问的路径,函数就是路径(接口)被访问后执行自己自定义相关逻辑函数) //(1)如果外部访问模式3,路径为:http://127.0.0.1:7777/pattern_three,那么外部访问该路径后,会调用DetectObjectDeal函数处理函数 httpServer.AddRoute( "/api/detectobject" , DetectObjectDeal); //(2)同理,如果外部访问模式2:根据外部系统传入的指定测试区域,执行区域内若干摄像头按指定间隔时间自动循环切换 httpServer.AddRoute( "/api/testarea" , TestAreaDeal); //(3)同理,如果外部访问模式1:根据外部系统传入的指定摄像头,执行指定摄像头的切换 httpServer.AddRoute( "/api/appointcamera" , AppointCameraDeal); httpServer.Start(); } //模式3:根据外部系统传入的指定监测对象,执行yolo目标监测 public static async Task< string > DetectObjectDeal(HttpListenerContext context) { string httpMethod = context.Request.HttpMethod; //获取外部访问接口使用的是什么方法(理应事先应约定:GET POST) Log4NetHelper.Info( "接口模拟测试-----请求方法判别:" + httpMethod); string responseString = "这是我初始给的内容" ; //接口被调用返回内容 //判别为GET请求 if (httpMethod.Equals( "GET" , StringComparison.OrdinalIgnoreCase)) { // 处理 GET 请求 //string queryString = context.Request.QueryString.ToString(); NameValueCollection queryString = context.Request.QueryString; if (queryString.HasKeys()) { // 处理路径上直接传参的情况 //后面要按照约定的请求传参格式,进行对应的解析。。。 Log4NetHelper.Info( "接口模拟测试-----GET请求带参数, 参数全部Keys内容:" + string .Join( ", " , queryString.AllKeys)); //假设模拟的GET请求参数 http://127.0.0.1:7777/detectobject?myname="chaochao"& myage=18 进行解析 //var queryParams = System.Web.HttpUtility.ParseQueryString(); //string paramValue1 = queryParams["myname"]; // string paramValue2 = queryParams["myage"]; //Log4NetHelper.Info("接口模拟测试-----GET请求带参数,参数解析:myname:" + paramValue1 + ";myage: " + paramValue2); // 构建日志信息,显示所有键值对 string logInfo = "接口模拟测试-----GET请求带参数, 具体键值内容:" ; foreach ( string key in queryString.AllKeys) { logInfo += key + ": " + queryString[key] + "; " ; } Log4NetHelper.Info(logInfo); // 解析查询字符串并执行相应的逻辑 string paramValue1 = queryString[ "myname" ]; int paramValue2 = Convert.ToInt32(queryString[ "myage" ]); Log4NetHelper.Info( "接口模拟测试-----GET请求带参数, 参数解析:myname: " + paramValue1 + "; myage: " + paramValue2); // 处理带参数的 GET 请求 responseString = "{\"code\":\"200\",\"message\":\"GET请求带参数处理成功\", \"myname\":\"" + paramValue1 + "\", \"myage\":\"" + paramValue2 + "\"}" ; } else { // 处理路径上不传参的情况 Log4NetHelper.Info( "接口模拟测试-----GET请求不带参数" ); responseString = "{\"code\":\"200\",\"message\":\"GET请求不带参数处理成功\"}" ; //可能直接处理相关业务逻辑。。。 } } //判别为POST请求 else if (httpMethod.Equals( "POST" , StringComparison.OrdinalIgnoreCase)) { // 处理 POST 请求 if (context.Request.HasEntityBody) { using (Stream body = context.Request.InputStream) { using (StreamReader reader = new StreamReader(body, context.Request.ContentEncoding)) { string postData = await reader.ReadToEndAsync(); // 读取 POST 数据 //处理数据 //。。。。。 //返回数据(约定返回给外部请求方) responseString = "{\"code\":\"200\",\"message\":\"处理成功\"}" ; //后面根据实际约定进行定义 Log4NetHelper.Info( "接口模拟测试-----POST请求数据:" + postData); } } } } return responseString; } |
3.运行winform程序,通过postman测试POST请求
4.运行winform程序,通过浏览器或postman测试GET请求
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】