HttpListener
这是一个简易的web服务端,具有以下优点:
1.不用IIS即可部署,也可以做成安装包,实现傻瓜式部署
2.可以内嵌到winform中,方便与界面交互,方便与硬件对接
HttpServer通用代码部分如下
using DoneWin.Common; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; namespace MyProject { public class HttpServer { HttpListener httpobj = new HttpListener(); HttpHandle _httpHandle; public HttpServer(string ip, int port, HttpHandle httpHandle) { try { Init(ip, port, httpHandle); } catch(Exception ex) { //LogHelper.logger.Error(ex); } } public string HostUrl { get { return httpobj.Prefixes.First(); } } public void Init(string ip, int port, HttpHandle httpHandle) { try { _httpHandle = httpHandle; httpobj.Stop(); httpobj.Prefixes.Clear(); httpobj.Prefixes.Add($"http://{ip}:{port}/"); httpobj.Start(); httpobj.BeginGetContext(Result, null); LogHelper.logger.Info("HttpServer初始化完毕。"); } catch (Exception ex) { //LogHelper.logger.Error(ex); } } private void Result(IAsyncResult ar) { try { httpobj.BeginGetContext(Result, null);//继续异步监听 //处理请求 var context = httpobj.EndGetContext(ar); var request = context.Request; var response = context.Response; response.ContentType = "text/plain;charset=UTF-8"; response.AddHeader("Content-type", "text/plain"); response.ContentEncoding = Encoding.UTF8; try { _httpHandle.Request = request; _httpHandle.Response = response; Type type = _httpHandle.GetType(); string methodName = request.RawUrl.Split('?').FirstOrDefault().Split('/').Last(); var method = type.GetMethod(methodName); if (method == null) { response.StatusDescription = "Method Not Found!"; response.StatusCode = 404; } else { var result = (Byte[])method.Invoke(_httpHandle, null); _httpHandle.WriteResponse(result); response.StatusDescription = "ok"; response.StatusCode = 200; } } catch(Exception ex) { response.StatusDescription = ex.Message; response.StatusCode = 500; } } catch(Exception ex) { //LogHelper.logger.Error(ex); } } public void Stop() { if (httpobj != null) { httpobj.Stop(); httpobj.Abort(); } } } public abstract class HttpHandle { public HttpListenerRequest Request; public HttpListenerResponse Response; public Dictionary<string, object> GetParams() { var paramDic = new Dictionary<string, object>(); try { var byteList = new List<byte>(); var byteArr = new byte[2048]; int readLen = 0; int len = 0; //接收客户端传过来的数据并转成字符串类型 do { readLen = Request.InputStream.Read(byteArr, 0, byteArr.Length); len += readLen; byteList.AddRange(byteArr); } while (readLen != 0); string data = Encoding.UTF8.GetString(byteList.ToArray(), 0, len); //form-data转Dictionary string rqContentType = "--" + Request.ContentType.Split('=').Last(); string[] paramNodes = data.Split(new string[] { rqContentType }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < paramNodes.Length; i++) { if (!paramNodes[i].Trim('\r', '\n').StartsWith("Content-Disposition")) continue; string[] dataLines = paramNodes[i].Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); string[] headers = dataLines[0].Split(';'); string key = headers[1].Split('=').Last().Trim('\"'); if (headers.Length == 2) paramDic.Add(key, dataLines[1]);//普通参数 else {//文件参数 string fileName = headers[2].Split('=').Last().Trim('\"'); string contentType = dataLines[1].Split(':').Last().Trim(); //因为data经过了解码,从中读取文件内容(比如图片)可能文件损坏,所以从byteList读取文件的byte数组 byte[] searchBytes = Encoding.UTF8.GetBytes(dataLines[0] + "\r\n" + dataLines[1] + "\r\n\r\n"); int byteStartIndex = IndexOf(byteList.ToArray(), searchBytes, 0) + searchBytes.Length; int byteEndIndex = IndexOf(byteList.ToArray(), Encoding.UTF8.GetBytes(rqContentType), byteStartIndex) - 1; byte[] fileBytes = new byte[byteEndIndex - byteStartIndex]; byteList.CopyTo(byteStartIndex, fileBytes, 0, fileBytes.Length); paramDic.Add(key, new FileParameter(fileBytes, fileName, contentType)); } } } catch (Exception ex) { //LogHelper.logger.Error(ex); } return paramDic; } public static int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex) { var index = 0; var startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex); if (startPos != -1) { while (startPos + index < searchWithin.Length) { if (searchWithin[startPos + index] == serachFor[index]) { index++; if (index == serachFor.Length) { return startPos; } } else { startPos = Array.IndexOf(searchWithin, serachFor[0], startPos + index); if (startPos == -1) { return -1; } index = 0; } } } return -1; } public virtual void WriteResponse(byte[] result) { try { using (var stream = Response.OutputStream) { stream.Write(result, 0, result.Length); } } catch (Exception ex) { //LogHelper.logger.Error(ex); } } } public class FileParameter { public byte[] File { get; set; } public string FileName { get; set; } public string ContentType { get; set; } public FileParameter(byte[] file) : this(file, null) { } public FileParameter(byte[] file, string filename) : this(file, filename, null) { } public FileParameter(byte[] file, string filename, string contenttype) { File = file; FileName = filename; ContentType = contenttype; } } }
接下来就是业务接口代码了
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Threading; namespace MyProject { public class myHttpHandle : HttpHandle { string apiPwd = string.Empty; public myHttpHandle (string pwd) { apiPwd = pwd; } public byte[] myApi1() { var jsonResult = new JsonResult { isSuccess = 0, msg = string.Empty }; try { var paramDic = GetParams(); if (!paramDic.ContainsKey("pwd") || !paramDic.ContainsKey("deviceType")) { jsonResult.msg = "参数错误"; return jsonResult.ToBytes(); } if (!string.IsNullOrEmpty(apiPwd) && apiPwd != paramDic["pwd"].ToString()) { jsonResult.msg = "密码错误"; return jsonResult.ToBytes(); } //文件参数存本地 foreach(string key in paramDic.Keys) { if (paramDic[key] is FileParameter) { var fileParam = (FileParameter)paramDic.Last().Value; File.WriteAllBytes($@"./{fileParam.FileName}", fileParam.File); } } jsonResult.data = new { name= "Jone"}; jsonResult.isSuccess = 1; } catch (Exception ex) { //LogHelper.logger.Error(ex); jsonResult.msg = "服务器内部错误"; } return jsonResult.ToBytes(); } } public class JsonResult { public int isSuccess { get; set; } public string msg { get; set; } public object data { get; set; } public string ToJson() { StringBuilder sb = new StringBuilder("{\"isSuccess\":"); sb.Append(isSuccess); sb.Append(",\"msg\":\""); sb.Append(msg); sb.Append("\",\"data\":{"); if(data is Person) { var obj = (Person)data; sb.Append("\"name\":"); sb.Append(obj.name); } sb.Append("}}"); return sb.ToString(); } public byte[] ToBytes() { string jsonResult = this.ToJson(); if (string.IsNullOrEmpty(jsonResult)) return new byte[0]; else return Encoding.UTF8.GetBytes(jsonResult); } } public class Person { public int name{ get; set; } } }
接口写完,就可以写启动代码了,这里以嵌入到winform为例的,所以在主窗口加载时初始化,窗口关闭时释放
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Windows.Forms; namespace MyProject { public partial class Form1 : Form { //系统字段 HttpServer httpSrv; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { try { httpSrv = new HttpServer("127.0.0.1", "8088", new myHttpHandle("Abc123")); } catch (Exception ex) { //LogHelper.logger.Error(ex); } } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { try { if (httpSrv != null) httpSrv.Stop(); } catch { } } } }
以上代码均是在.net 3.5基础上编写,win7及以上windows系统就免了安装.net framework
另外,在做相关项目时还解决了一个跨域问题,问题描述如下
No 'Access-Control-Allow-Origin' header is present on the requested resource
解决办法是加上如下几行代码
response.AddHeader("Access-Control-Allow-Origin", "*");
response.AddHeader("Access-Control-Allow-Method", "*");
response.AddHeader("Access-Control-Allow-Headers", "Content-Type");
response.AddHeader("Access-Control-Max-Age", "3600");
response.AddHeader("Cache-Control", "no-cache");