65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 示例 HTTP 服务器框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (31投票s)

2007年1月8日

2分钟阅读

viewsIcon

261150

downloadIcon

12235

HTTP 服务器抽象类,用于提供 HTTP 内容。

Sample HTTPServer.

引言

我们需要从我们的应用程序向常规浏览器或另一个 HTTP 客户端提供 HTTP 内容。 让我们创建一个抽象类来处理 HTTP 请求,并让派生类处理响应,而无需担心其他任何事情。

使用代码

我们将使用两个类来构建我们的 HTTP 服务器框架:CSHTTPServer 类和 CsHTTPRequest 类。 CSHTTPServer 将是每个 CsHTTPRequest 请求的父类,并将包含服务器信息,如监听端口、监听套接字、运行线程实例、响应状态和服务器名称。 它将实现启动、停止和恢复服务器的方法,并定义抽象方法 OnResponse,该方法应在派生类中实现以实际提供内容。

让我们创建 CSHTTPServer

  
public abstract class CSHTTPServer
{
  private int portNum = 8080;
  private TcpListener listener;
  System.Threading.Thread Thread;

  public Hashtable respStatus;

  public string Name = "MyHTTPServer/1.0.*";

  public bool IsAlive
  {
     get 
     {
       return this.Thread.IsAlive; 
     }
  }

  public CSHTTPServer()
  {
     //
     respStatusInit();
  }

  public CSHTTPServer(int thePort)
  {
     portNum = thePort;
     respStatusInit();
  }

  private void respStatusInit()
  {
     respStatus = new Hashtable();
     
     respStatus.Add(200, "200 Ok");
     respStatus.Add(201, "201 Created");
     respStatus.Add(202, "202 Accepted");
     respStatus.Add(204, "204 No Content");

     respStatus.Add(301, "301 Moved Permanently");
     respStatus.Add(302, "302 Redirection");
     respStatus.Add(304, "304 Not Modified");
     
     respStatus.Add(400, "400 Bad Request");
     respStatus.Add(401, "401 Unauthorized");
     respStatus.Add(403, "403 Forbidden");
     respStatus.Add(404, "404 Not Found");

     respStatus.Add(500, "500 Internal Server Error");
     respStatus.Add(501, "501 Not Implemented");
     respStatus.Add(502, "502 Bad Gateway");
     respStatus.Add(503, "503 Service Unavailable");
  }

  public void Listen() 
  {
     bool done = false;
    
     listener = new TcpListener(portNum);
     
     listener.Start();

     WriteLog("Listening On: " + portNum.ToString());

     while (!done) 
     {
       WriteLog("Waiting for connection...");
       CsHTTPRequest newRequest
= new CsHTTPRequest(listener.AcceptTcpClient(),this); Thread Thread = new Thread(new ThreadStart(newRequest.Process)); Thread.Name = "HTTP Request"; Thread.Start(); } } public void WriteLog(string EventMessage) { Console.WriteLine(EventMessage); } public void Start() { // CSHTTPServer HTTPServer = new CSHTTPServer(portNum); this.Thread = new Thread(new ThreadStart(this.Listen)); this.Thread.Start(); } public void Stop() { listener.Stop(); this.Thread.Abort(); } public void Suspend() { this.Thread.Suspend(); } public void Resume() { this.Thread.Resume(); } public abstract void OnResponse(ref HTTPRequestStruct rq,
ref HTTPResponseStruct rp); }

一旦服务器通过调用 Start() 方法启动,控制权将传递给在新的线程中运行的 Listen() 方法,该方法反过来为每个接受的 listener 请求创建一个新的线程来运行 newRequest.Process()

现在创建 CsHTTPRequest

       
   enum RState
   {
      METHOD, URL, URLPARM, URLVALUE, VERSION, 
      HEADERKEY, HEADERVALUE, BODY, OK
   };

   enum RespState
   {
      OK = 200, 
      BAD_REQUEST = 400,
      NOT_FOUND = 404
   }

   public struct HTTPRequestStruct
   {
      public string Method;
      public string URL;
      public string Version;
      public Hashtable Args;
      public bool Execute;
      public Hashtable Headers;
      public int BodySize;
      public byte[] BodyData;
   }

   public struct HTTPResponseStruct
   {
      public int status;
      public string version;
      public Hashtable Headers;
      public int BodySize;
      public byte[] BodyData;
      public System.IO.FileStream fs;
   }

   /// <SUMMARY>
   /// Summary description for CsHTTPRequest.
   /// </SUMMARY>
   public class CsHTTPRequest
   {
      private TcpClient client;

      private RState ParserState;

      private HTTPRequestStruct HTTPRequest;

      private HTTPResponseStruct HTTPResponse;

      byte[] myReadBuffer;

      CSHTTPServer Parent;

      public CsHTTPRequest(TcpClient client, CSHTTPServer Parent) 
      {
         this.client = client;
         this.Parent = Parent;

         this.HTTPResponse.BodySize = 0;
      }

      public void Process()
      {
         myReadBuffer = new byte[client.ReceiveBufferSize];
         String myCompleteMessage = "";
         int numberOfBytesRead = 0;

         Parent.WriteLog("Connection accepted. Buffer: " + 
client.ReceiveBufferSize.ToString()); NetworkStream ns = client.GetStream(); string hValue = ""; string hKey = ""; try { // binary data buffer index int bfndx = 0; // Incoming message may be larger than the buffer size. do { numberOfBytesRead = ns.Read(myReadBuffer, 0,
myReadBuffer.Length); myCompleteMessage = String.Concat(myCompleteMessage, Encoding.ASCII.GetString(myReadBuffer, 0,
numberOfBytesRead)); // read buffer index int ndx = 0; do { switch ( ParserState ) { case RState.METHOD: if (myReadBuffer[ndx] != ' ') HTTPRequest.Method += (char)myReadBuffer[ndx++]; else { ndx++; ParserState = RState.URL; } break; case RState.URL: if (myReadBuffer[ndx] == '?') { ndx++; hKey = ""; HTTPRequest.Execute = true; HTTPRequest.Args = new Hashtable(); ParserState = RState.URLPARM; } else if (myReadBuffer[ndx] != ' ') HTTPRequest.URL += (char)myReadBuffer[ndx++]; else { ndx++; HTTPRequest.URL
= HttpUtility.UrlDecode(HTTPRequest.URL); ParserState = RState.VERSION; } break; case RState.URLPARM: if (myReadBuffer[ndx] == '=') { ndx++; hValue=""; ParserState = RState.URLVALUE; } else if (myReadBuffer[ndx] == ' ') { ndx++; HTTPRequest.URL
= HttpUtility.UrlDecode(HTTPRequest.URL); ParserState = RState.VERSION; } else { hKey += (char)myReadBuffer[ndx++]; } break; case RState.URLVALUE: if (myReadBuffer[ndx] == '&') { ndx++; hKey=HttpUtility.UrlDecode(hKey); hValue=HttpUtility.UrlDecode(hValue); HTTPRequest.Args[hKey] =
HTTPRequest.Args[hKey] != null ? HTTPRequest.Args[hKey] + ", " + hValue :
hValue; hKey=""; ParserState = RState.URLPARM; } else if (myReadBuffer[ndx] == ' ') { ndx++; hKey=HttpUtility.UrlDecode(hKey); hValue=HttpUtility.UrlDecode(hValue); HTTPRequest.Args[hKey] =
HTTPRequest.Args[hKey] != null ? HTTPRequest.Args[hKey] + ", " + hValue :
hValue; HTTPRequest.URL
= HttpUtility.UrlDecode(HTTPRequest.URL); ParserState = RState.VERSION; } else { hValue += (char)myReadBuffer[ndx++]; } break; case RState.VERSION: if (myReadBuffer[ndx] == '\r') ndx++; else if (myReadBuffer[ndx] != '\n') HTTPRequest.Version += (char)myReadBuffer[ndx++]; else { ndx++; hKey = ""; HTTPRequest.Headers = new Hashtable(); ParserState = RState.HEADERKEY; } break; case RState.HEADERKEY: if (myReadBuffer[ndx] == '\r') ndx++; else if (myReadBuffer[ndx] == '\n') { ndx++; if (HTTPRequest.Headers["Content-Length"] != null) { HTTPRequest.BodySize = Convert.ToInt32(HTTPRequest.Headers["Content-Length"]); this.HTTPRequest.BodyData
= new byte[this.HTTPRequest.BodySize]; ParserState = RState.BODY; } else ParserState = RState.OK; } else if (myReadBuffer[ndx] == ':') ndx++; else if (myReadBuffer[ndx] != ' ') hKey += (char)myReadBuffer[ndx++]; else { ndx++; hValue = ""; ParserState = RState.HEADERVALUE; } break; case RState.HEADERVALUE: if (myReadBuffer[ndx] == '\r') ndx++; else if (myReadBuffer[ndx] != '\n') hValue += (char)myReadBuffer[ndx++]; else { ndx++; HTTPRequest.Headers.Add(hKey, hValue); hKey = ""; ParserState = RState.HEADERKEY; } break; case RState.BODY: // Append to request BodyData Array.Copy(myReadBuffer, ndx,
this.HTTPRequest.BodyData, bfndx, numberOfBytesRead - ndx); bfndx += numberOfBytesRead - ndx; ndx = numberOfBytesRead; if ( this.HTTPRequest.BodySize <= bfndx) { ParserState = RState.OK; } break; //default: // ndx++; // break; } } while(ndx < numberOfBytesRead); } while(ns.DataAvailable); // Print out the received message to the console. Parent.WriteLog("You received the following message : \n" + myCompleteMessage); HTTPResponse.version = "HTTP/1.1"; if (ParserState != RState.OK) HTTPResponse.status = (int)RespState.BAD_REQUEST; else HTTPResponse.status = (int)RespState.OK; this.HTTPResponse.Headers = new Hashtable(); this.HTTPResponse.Headers.Add("Server", Parent.Name); this.HTTPResponse.Headers.Add("Date", DateTime.Now.ToString("r")); // if (HTTPResponse.status == (int)RespState.OK) this.Parent.OnResponse(ref this.HTTPRequest,
ref this.HTTPResponse); string HeadersString = this.HTTPResponse.version + " " + this.Parent.respStatus[this.HTTPResponse.status] + "\n"; foreach (DictionaryEntry Header in this.HTTPResponse.Headers) { HeadersString += Header.Key + ": " + Header.Value + "\n"; } HeadersString += "\n"; byte[] bHeadersString = Encoding.ASCII.GetBytes(HeadersString); // Send headers ns.Write(bHeadersString, 0, bHeadersString.Length); // Send body if (this.HTTPResponse.BodyData != null) ns.Write(this.HTTPResponse.BodyData, 0,
this.HTTPResponse.BodyData.Length); if (this.HTTPResponse.fs != null) using (this.HTTPResponse.fs) { byte[] b = new byte[client.SendBufferSize]; int bytesRead; while ((bytesRead
= this.HTTPResponse.fs.Read(b,0,b.Length)) > 0) { ns.Write(b, 0, bytesRead); } this.HTTPResponse.fs.Close(); } } catch (Exception e) { Parent.WriteLog(e.ToString()); } finally { ns.Close(); client.Close(); if (this.HTTPResponse.fs != null) this.HTTPResponse.fs.Close(); Thread.CurrentThread.Abort(); } } }

Process() 方法解析 HTTP 请求,如果未发现任何错误请求,则调用父 HTTP 服务器的 OnResponse 方法,传递请求和响应变量进行处理。 最后,如果找到响应,则将其提供给客户端。

这里有一个 HTTP 协议的图表,希望可以帮助你理解 switch 语句

Sample Image - maximum width is 600 pixels

关注点

就这样!它对我有用。 我使用这个抽象类来创建一个小的 HTTP 服务器。 我们使用一些技巧来为应用程序创建与默认应用程序不同的外观,并且你也会找到创建托盘图标的方法。

历史

示例和源代码的总大小已大大减少。 修复了错误:由于错误的尺寸缓冲区,文件响应被截断!

© . All rights reserved.