C# 示例 HTTP 服务器框架






4.44/5 (31投票s)
2007年1月8日
2分钟阅读

261150

12235
HTTP 服务器抽象类,用于提供 HTTP 内容。
引言
我们需要从我们的应用程序向常规浏览器或另一个 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
语句
关注点
就这样!它对我有用。 我使用这个抽象类来创建一个小的 HTTP 服务器。 我们使用一些技巧来为应用程序创建与默认应用程序不同的外观,并且你也会找到创建托盘图标的方法。
历史
示例和源代码的总大小已大大减少。 修复了错误:由于错误的尺寸缓冲区,文件响应被截断!