简单的HTTP服务实现

最近因工作需要为外部公司提供http服务,由于内容比较少,同时为了方便安装,就想着自己写一个简单的服务器。

思路是将一个Http服务器嵌入一个Windows Service程序中,于是在网上找了很多资料和源码。C#中实现http服务一般使用Socket或TcpListener两种,本文使用的是Socket方式。

一、HTTP报文格式

Post报文:

POST /Adapter.KeyInitialization HTTP/1.1
Content-Length: 191
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: 115.156.249.117:11250
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.1 (Java/1.7.0_45)
Accept-Encoding: gzip,deflate

postdata=%7B%22key_extra_info%22%3A%22zehge%22%2C%22key_name%22%3A%22%E9%92%A5%E5%8C%9935313032-5956-4331-3031-3039ffffffff%22%2C%22key_rfid%22%3A%2235313032-5956-4331-3031-3039ffffffff%22%7D

熟悉编码的童鞋会发现,上面postdata=之后的部分其实是Url编码,所以在解析时需要用到HttpUtility.UrlDecode(String urlEncodeStr);

【注意】post有两种传参数的方式

1、参数放在url中,Content-Length值为0。

请求报文:

POST http://192.168.80.194:8080/JBPlatform/DFESerUpLoadData.posttest?jsonData=tetet HTTP/1.1
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache
Postman-Token: 202cd59d-b2e2-4370-a88c-8e7abaf7a8d9
User-Agent: PostmanRuntime/7.6.0
Accept: */*
Host: 192.168.80.194:8080
accept-encoding: gzip, deflate
content-length: 0
Connection: keep-alive

返回报文:

HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 64
Date: Thu, 23 Apr 2020 03:46:50 GMT

{"data":"成功","jsonData":"tetet","result":1,"runStatus":null}

2、参数放在body中,Content-Length需根据body计算长度

请求报文:

POST http://192.168.80.194:8080/JBPlatform/DFESerUpLoadData.posttest HTTP/1.1
Content-Type: application/x-www-form-urlencoded
cache-control: no-cache
Postman-Token: be9877fa-bad6-49db-82cf-5a993faf2fc8
User-Agent: PostmanRuntime/7.6.0
Accept: */*
Host: 192.168.80.194:8080
accept-encoding: gzip, deflate
content-length: 15
Connection: keep-alive

jsonData=tetete

返回报文:

HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 65
Date: Thu, 23 Apr 2020 03:31:36 GMT

{"data":"成功","jsonData":"tetete","result":1,"runStatus":null}

Get报文:

GET /  HTTP/1.1
Content-Length: 191
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: 115.156.249.117:11250
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.1 (Java/1.7.0_45)
Accept-Encoding: gzip,deflate

二、简单HTTP服务器的实现

1、根据报文格式写一个解析报文的类

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
/// <summary>
/// 描述:HTTP请求类,用于解析HTTP报文
/// 编写:gc
/// 日期:2016/3/8
/// 版本:1.0.1.3
/// </summary>
public class MyHTTPRequest
{
    public string Method { get; private set; }
    public string Url { get; private set; }
    public string Protocol { get; private set; }
    private Dictionary<string, string> properties = null;

    public Dictionary<string, string> Properties
    {
        get
        {
            if (properties == null)
                properties = new Dictionary<string, string>();
            return properties;
        }
    }

    public string Parameter { get; private set; }

    public MyHTTPRequest()
    {
        //Nothing to do!
    }

    public MyHTTPRequest(string requestString)
    {
        using (StringReader sr = new StringReader(requestString))
        {
            var line = sr.ReadLine();
            if (!IsNullOrWhiteSpace(line))
            {
                string[] headerInfo = line.Split(new char[] { ' ' });
                if (headerInfo.Length == 3)
                {
                    SetHeader(headerInfo[0], headerInfo[1], headerInfo[2]);
                    for (line = sr.ReadLine(); !IsNullOrWhiteSpace(line); line = sr.ReadLine())
                    {
                        string[] tokens = line.Split(new string[] { ": " }, StringSplitOptions.None);
                        if (tokens.Length == 2)
                        {
                            Properties.Add(tokens[0], tokens[1]);
                        }
                        else
                        {
                            //Console.WriteLine(line); //打印调试信息
                        }
                    }

                    switch (this.Method.ToUpper())
                    {
                        case "GET":
                            Parameter = string.Empty;
                            break;
                        case "POST":
                            {
                                try
                                {
                                    string tempStr = sr.ReadToEnd().ToString().Trim();

                                    SKMPInput input = new SKMPInput(tempStr);
                                    Parameter = input.InputParameter;
                                    //SKMPInput input = JSONConvertHelper.ToObject<SKMPInput>(tempStr);
                                    //Parameter = JSONConvertHelper.ToJSONString(input.postdata);
                                }
                                catch (Exception ex)
                                {
                                    string tempStr = sr.ReadToEnd().ToString().Trim();
                                    SKMPInput input = new SKMPInput(tempStr);
                                    Parameter = input.InputParameter;

                                    //SKMPInput input = JSONConvertHelper.ToObject<SKMPInput>(tempStr);
                                    //Parameter = JSONConvertHelper.ToJSONString(input.postdata);
                                }

                                //Parameter = sr.ReadToEnd().ToString().Trim();
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }

    public void SetHeader(string method, string url, string protocol)
    {
        this.Method = method;
        this.Url = url;
        this.Protocol = protocol;
    }

    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("Method:{0}<br/>", Method);
        sb.AppendFormat("Url:{0}<br/>", Url);
        sb.AppendFormat("Protocol:{0}<br/>", Protocol);
        foreach (var item in Properties)
        {
            sb.AppendFormat("{0}:{1}<br/>", item.Key, item.Value);
        }
        sb.AppendFormat("Parameter:{0}<br/>", Parameter);
        return sb.ToString();
    }
    public static bool IsNullOrWhiteSpace(string value)
    {
        if (value != null)
        {
            for (int i = 0; i < value.Length; i++)
            {
                if (!char.IsWhiteSpace(value[i]))
                {
                    return false;
                }
            }
        }
        return true;
    }
}

2、服务器主体程序

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
/// <summary>
/// 描述:HTTP服务器
/// 编写:gc
/// 日期:2016/3/8
/// 版本:1.0.1.3
/// </summary>
public class MyHTTPServer
{
    private string companyName = "**公司";
    private string localServerInfo = "MyHTTPServer/1.0";
    private string localServerIP = "localhost";
    private int localServerPort = 8080;
    private string localServerContentType = "image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";
    private Encoding localServerEncoding = Encoding.GetEncoding("gb2312");
    private TicketService ticketService = null;

    private string targetHostIP;
    private int targetHostPort = 11250;

    private Thread m_Thread; //HTTP服务主线程
    public bool IsAlive
    {
        get
        {
            return this.m_Thread.IsAlive;
        }
    }
    /// <summary>
    /// 监听标志位
    /// </summary>
    private bool isRun = false;
    Socket serverSocket;
    public CookieContainer CookieContainer { get; set; }

    public MyHTTPServer()
    {
        Initialize();
    }

    /// <summary>
    /// 初始化服务器参数
    /// </summary>
    private void Initialize()
    {
        this.companyName = ServerGlobal.ServerGlobalInstance.CompanyName;
        this.localServerInfo = ServerGlobal.ServerGlobalInstance.LocalServerInfo;
        this.localServerIP = ServerGlobal.ServerGlobalInstance.LocalServerIP;
        this.localServerPort = ServerGlobal.ServerGlobalInstance.LocalServerPort;
        this.localServerContentType = ServerGlobal.ServerGlobalInstance.LocalServerContentType;
        this.localServerEncoding = ServerGlobal.ServerGlobalInstance.LocalServerEncoding;
        this.targetHostIP = ServerGlobal.ServerGlobalInstance.TargetHostIP;
        this.targetHostPort = ServerGlobal.ServerGlobalInstance.TargetHostPort;

        ticketService = TicketService.TicketServiceInstance;
    }

    /// <summary>
    /// 启动服务器
    /// </summary>
    public void Start()
    {
        isRun = true;
        this.m_Thread = new Thread(new ThreadStart(this.Listen));
        this.m_Thread.Start();

        ticketService.Start();
    }

    /// <summary>
    /// 停止服务器
    /// </summary>
    public void Stop()
    {
        isRun = false;
        this.m_Thread.Abort();
        serverSocket.Close();//释放Socket占用的端口号

        ticketService.Stop();
    }

    /// <summary>
    /// 监听
    /// </summary>
    private void Listen()
    {
        try
        {
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, this.localServerPort);
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipep);
            serverSocket.Listen(10);
            while (isRun)
            {
                Socket clientSocket = serverSocket.Accept();
                ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), clientSocket);
            }
        }
        catch (Exception ex)
        {
            MyServerRunLog.WriteRunLog("listening Error: " + ex.Message);
        }
    }

    /// <summary>
    /// 接收数据
    /// </summary>
    /// <param name="socket">Socket</param>
    private void ReceiveData(object socket)
    {
        Socket clientSocket = socket as Socket;
        Byte[] buffer = new Byte[8192]; //Socket默认缓冲区为8K,当数据量大于8K时此处可能会产生Bug
        IPEndPoint clientep = (IPEndPoint)clientSocket.RemoteEndPoint;
        string clientMessage = string.Empty;

        try
        {
            clientSocket.Receive(buffer, 0, buffer.Length, SocketFlags.None);
            clientMessage = localServerEncoding.GetString(buffer);

            if (IsNullOrWhiteSpace(clientMessage))
            {
            }
            byte[] data = WriteSuccessResponse(clientMessage);
            clientSocket.Send(data, data.Length, SocketFlags.None);

        }
        catch (Exception ex)
        {
            StringBuilder sb = new StringBuilder();
            WriteFailure(sb);
            sb.AppendFormat("<html><head><title>Server throw an exception:{1}</title></head><body>{0}</body></html>", ex.ToString(), ex.GetType().FullName);
            var data = localServerEncoding.GetBytes(sb.ToString());
            clientSocket.Send(data, data.Length, SocketFlags.None);
        }
        clientSocket.Shutdown(SocketShutdown.Both);
    }

    private byte[] WriteSuccessResponse(string clientMessage)
    {
        MyHTTPRequest request = new MyHTTPRequest(clientMessage);
        StringBuilder sb = new StringBuilder();

        sb.AppendFormat("{0} 200 ok
", request.Protocol);

        if (null != request.Method)
        {
            switch (request.Method.ToUpper())
            {
                case "GET":
                    {
                        sb.AppendLine("connection: close");
                        sb.AppendLine();
                        sb.AppendFormat("<html><head><title>Connect Sucess {1}</title></head><body><h2>Welcome to my http server</h2>{0}<h3><div><h3>Your requested content:</h3>{2}</div>Author: <a href='http://www.baidu.com/'>百度</a></h3></body></html>", request.ToString(), request.Url, clientMessage);
                        return Encoding.GetEncoding("gb2312").GetBytes(sb.ToString()); //
                    }
                    break;
                case "POST":
                    {
                        string returnValue = DealWithUrl(request);
                        StringBuilder contentBody = new StringBuilder();
                        contentBody = contentBody.Append(returnValue);
                        //contentBody = contentBody.AppendFormat("<html><head><title>You request Url is {0}</title></head><body>{1}</body></html>", request.Url, returnValue);
                        int contentLength = localServerEncoding.GetBytes(contentBody.ToString()).Length;

                        sb.AppendLine(string.Format("Server: {0} ", localServerInfo));
                        sb.AppendLine(string.Format("Date: {0} ", DateTime.Now.GetDateTimeFormats('r')[0].ToString()));
                        sb.AppendLine(string.Format("Content-Type: {0} ", localServerContentType));
                        sb.AppendLine(string.Format("Last-Modified: {0} ", DateTime.Now.GetDateTimeFormats('r')[0].ToString()));
                        sb.AppendLine(string.Format("Content-Length: {0} ", contentLength));

                        sb.AppendLine();
                        sb.AppendLine(contentBody.ToString());
                    }
                    break;
                default:
                    sb.AppendFormat("<html><head><title>You request is {0}</title></head><body><h2>Welcome to my http server</h2>{1}<h3>Author: <a href='mailto:http://www.baidu.com/'>百度</a></h3></body></html>", request.Url, request.ToString());
                    break;
            }
        }

        return localServerEncoding.GetBytes(sb.ToString());

    }

    /// <summary>
    /// 根据Url调用对应的接口方法处理数据
    /// </summary>
    /// <param name="request"></param>
    /// <returns>JSON格式字符串</returns>
    private string DealWithUrl(MyHTTPRequest request)
    {
        string returnValue = "未找到对应的接口方法:" + request.Url;
        switch (request.Url)//.ToUpper()) 
        {
            case "/Local.GetInfo":
                returnValue = GetInfo();

            case "/Local.WorkWithInput":
                returnValue = WorkWithInput(request.Parameter);
                break;


                break;
            default:
                break;
        }
        return returnValue;
    }

    #region Local.xxx 表示提供给外部系统调用的接口
    /// <summary>
    /// 1)获取**信息
    /// </summary>
    /// <returns></returns>
    public string GetInfo()
    {

    }


    #endregion

    public void WriteFailure(StringBuilder sw)
    {
        sw.AppendLine("http/1.0 404 file not found");
        sw.AppendLine("connection: close");
        sw.AppendLine();
    }

    #region Target.xxxx 表示本地系统调用外部系统的接口

    private string UploadInfo(UploadInput input)
    {

    }


    #endregion
    private string Post(string postUrl, string paramData)
    {
        string result = string.Empty;
        Stream stream = null;
        StreamReader sr = null;
        HttpWebResponse response = null;
        try
        {
            byte[] byteArray = Encoding.UTF8.GetBytes(paramData);
            HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(new Uri(postUrl));
            webReq.Method = "POST";
            webReq.ContentType = localServerContentType;
            //webReq.ContentType = "application/x-www-form-urlencoded";
            webReq.Accept = "text/json";
            webReq.UserAgent = localServerInfo;
            webReq.ContentLength = byteArray.Length;
            webReq.ServicePoint.Expect100Continue = false;
            webReq.CookieContainer = CookieContainer;
            stream = webReq.GetRequestStream();
            stream.Write(byteArray, 0, byteArray.Length);
            response = (HttpWebResponse)webReq.GetResponse();
            if (response.StatusCode == HttpStatusCode.OK)
            {
                response.Cookies = CookieContainer.GetCookies(webReq.RequestUri);
                sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
                result = sr.ReadToEnd();
            }
            else
            {
                Debug.WriteLine(response.StatusCode);
            }
        }
        finally
        {
            if (sr != null)
            {
                sr.Close();
            }
            if (response != null)
            {
                response.Close();
            }
            if (stream != null)
            {
                stream.Close();
            }
        }
        return result;
    }

    public static bool IsNullOrWhiteSpace(string value)
    {
        if (value != null)
        {
            for (int i = 0; i < value.Length; i++)
            {
                if (!char.IsWhiteSpace(value[i]))
                {
                    return false;
                }
            }
        }
        return true;
    }

    #region JSONObject转换
    public static string ToJSON(object value)
    {
        return Newtonsoft.Json.JsonConvert.SerializeObject(value, Formatting.Indented, new JsonSerializerSettings
        {
            ContractResolver = new LowerCasePropertyNamesContractResolver(),
            DateFormatString = "yyyy-MM-dd HH:mm:ss.fff",
            ReferenceLoopHandling = ReferenceLoopHandling.Serialize
        });
    }

    private class LowerCasePropertyNamesContractResolver : DefaultContractResolver
    {
        public LowerCasePropertyNamesContractResolver()
        : base(true)
        {
        }

        protected override string ResolvePropertyName(string propertyName)
        {
            return propertyName.ToLower();
        }
    }

    public static T ToObject<T>(string json)
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json);
        //return fastJSON.JSON.ToObject<T>(json);
    }
    #endregion


}



基于Socket的服务器程序基本完成。

原文地址:https://www.cnblogs.com/cheng2015/p/5197899.html