最近在学习asp.net,在学习这部分之前,我觉得对iis的理解是很重要的,所以记录一下简单模拟IIS的过程和代码。
一. 模拟IIS流程:
首先模拟IIS服务器,最基本的就是对浏览器的请求进行处理,并将处理结果返还给浏览器,浏览器在对服务器响应的结果进行解析,并呈现在浏览器中,所以具体步骤如下所示:
1. 编写Socket,用于监听并接受浏览器发过来的请求,以及向浏览器发送响应内容。
2. 接受浏览器的请求报文,并进行解析,获取有用的报文信息。
3. 根据请求报文的信息进行响应,响应时,包括处理响应头和处理响应体。
3.1 响应头处理
3.2 响应体处理
4. 编写响应的网页,进行测试。
二. 代码和说明
编写Socket
private void btnBind_Click(object sender, EventArgs e)
{
Socket bindSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(txtIp.Text.Trim());
IPEndPoint port = new IPEndPoint(ip, int.Parse(txtPort.Text));
bindSocket.Bind(port);
bindSocket.Listen(10);
lblBind.Text = "正在监听...";
lblBind.ForeColor = Color.Green;
//创建线程池进行监听
ThreadPool.QueueUserWorkItem(new WaitCallback(conSck =>
{
Socket acceptSocket = conSck as Socket;
while (true)
{
Socket recieveSocket = acceptSocket.Accept();
byte[] buffer = new byte[1024 * 1024 * 2];
int r = recieveSocket.Receive(buffer);
string str = Encoding.UTF8.GetString(buffer, 0, r);
txtInfo.Text = str;
//处理请求后响应
ProcessRequest(str, recieveSocket);
}
}),bindSocket);
}
这里的ProcessRequest是回发响应数据的函数。
解析请求报文
解析请求报文时,为了能更好的管理请求报文和响应报文,我们对这两部分进行了封装,并封装一个上下文类,对其进行管理。即这个上下文HttpContext类相当于一个中转站,它负责向浏览器分发响应体,以及向服务器分发请求体。
HttpContext:
class HttpContext
{
public HttpContext(string str)
{
this.HttpRequest = new HttpRequest(str);
this.HttpResponse = new HttpResponse(HttpRequest);
}
public HttpRequest HttpRequest{get;set;}
public HttpResponse HttpResponse { get; set; }
}
这样处理请求报文类HttpRequest将请求报文处理好后提交给HttpContext,处理响应体的类就可以从HttpContext获取相应的处理后的请求体了。下面是请求类对请求体的处理。
HttpRequest:
class HttpRequest
{
private string str;
public HttpRequest(string str)
{
string[] strSplit = str.Split(new string[] { "\r\n" }, StringSplitOptions.None);
string[] strRequest = strSplit[0].Split(' ');
this.RequestMethod = strRequest[0];
this.RequestUrl = strRequest[1];
this.HttpVersion = strRequest[2];
}
public string RequestMethod { get; set; }
public string HttpVersion { get; set; }
public string RequestUrl { get; set; }
}
由于浏览器对服务器的请求内容比较多,我们只获取一下请求内容中的请求方法,请求地址,以及请求的http版本号。其实这里只用到了了请求的地址,即浏览器所请求的页面。
响应处理
响应类拿到请求类解析后的请求属性后,会对浏览器的请求进行处理(主要是响应体处理,即请求的页面处理),然后将处理结果与响应类的响应头中所必须的属性一起回发给HttpContext,再由Sockt将HttpContext中接受的响应发送给浏览器。那么如果将响应处理的内容写到响应类中会造成程序和层次的混乱,以及降低了程序的维护性。所以这里再封装一个类来对请求内容处理。
为什么要对请求内容处理呢,因为浏览器请求的页面分为静态页面和动态页面,那么如果是静态页面,我们需要从服务器本地获取到浏览器所请求的页面地址,如果是动态页面,我们需根据请求的URL动态获取.cs等动态页面文件。下面是HttpApplication类,用于处理静态页面和动态页面。
HttpApplication:
class HttpApplication
{
public void ProcessRequest(HttpContext context)
{
string extention=Path.GetExtension(context.HttpRequest.RequestUrl);
if (extention == ".aspx")
{
HandleDynamicPage(context);
}
else
{
HandleStaticPage(context);
}
}
private void HandleStaticPage(HttpContext context)
{
string namepace = MethodBase.GetCurrentMethod().DeclaringType.Namespace;
string path1 = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string path = Path.Combine(path1, "website", context.HttpRequest.RequestUrl.TrimStart('/'));
if (File.Exists(path))
{
context.HttpResponse.ResponseBody = File.ReadAllBytes(path);
}
else
{
context.HttpResponse.ResponseBody = new byte[] { 0 };
}
}
private void HandleDynamicPage(HttpContext context)
{
string className=Path.GetFileNameWithoutExtension(context.HttpRequest.RequestUrl);
string ObjectName = MethodBase.GetCurrentMethod().DeclaringType.Namespace + "." + className;
IDynamicPages dynamicPage =(IDynamicPages) Assembly.GetExecutingAssembly().CreateInstance(ObjectName);
if (dynamicPage != null)
{
dynamicPage.ProcessRequest(context);
}
}
响应类
处理完毕后,结合响应类就可以将响应内容回发了。
HttpResponse:
class HttpResponse
{
private HttpRequest httpRequest;
public HttpRequest HttpRequest
{
get { return httpRequest; }
set { httpRequest = value; }
}
public HttpResponse(HttpRequest httpRequest)
{
this.HttpRequest = httpRequest;
}
public byte[] ResponseHeader
{
get
{
return GetResponseHeader();
}
}
public byte[] ResponseBody { get; set; }
public string ContentLenth
{
get
{
if (ResponseBody != null)
{
return ResponseBody.Length.ToString();
}
else return string.Empty;
}
set { ContentLenth = value; }
}
public string ContentType
{
get { return GetContentType(HttpRequest.RequestUrl); }
set { ContentType = value; }
}
private byte[] GetResponseHeader()
{
//HTTP/1.1 200 OK
//Content-Type: text/html
//Content-Length: 913
StringBuilder strHeader = new StringBuilder();
strHeader.AppendLine("HTTP/1.1 200 OK");
strHeader.AppendLine("Content-Type: " + ContentType);
strHeader.AppendLine("Content-Length: " + ContentLenth);
strHeader.AppendLine();
return Encoding.UTF8.GetBytes(strHeader.ToString());
}
private string GetContentType(string p)
{
string urlExtension = Path.GetExtension(p);
string content_Type = string.Empty;
switch (urlExtension)
{
case ".aspx":
case ".html":
case ".htm":
content_Type = "text/html; charset=utf-8";
break;
case ".png":
content_Type = "image/png";
break;
case ".gif":
content_Type = "image/gif";
break;
case ".jpg":
case ".jpeg":
content_Type = "image/jpeg";
break;
case ".css":
content_Type = "text/css";
break;
case ".js":
content_Type = "application/x-javascript";
break;
default:
content_Type = "text/plain";
break;
}
return content_Type;
}
}
响应类中包括响应头和响应体,为了简单,我们同样只响应一些必要的内容,响应头包括响应是否成功,响应页面类型(html,aspx,css,jpg等),以及响应体字节长度。响应体自然是响应页面的内容了。这里交给了HttpApplication处理了。
获取动态页面内容
获取静态页面的内容在HttpApplication中有,我们只需建好相关的页面就可以了。那么怎么处理动态页面呢,这里以.aspx为例,.aspx页面在程序中可以是一个.cs文件,那么我们就要建立相应的.cs文件了。但是建立好了.cs文件,程序怎样获取呢,因为项目中有很多.cs文件,我们怎么判断哪个是相应的.aspx呢,怎样获取相应的类对象呢。这里可以让所有的只要是.aspx的类都继承一个IDynamicPages的接口,并实现接口中的ProcessRequest方法,然后可以通过反射动态页面名称获取相应的类,并将其强转成IDynamicPages类型。这样就获取到了该对象,并执行该对象中的ProcessRequest(用于获取页面内容)方法。下面是.aspx页面类:
class IndexPage : IDynamicPages
{
public void ProcessRequest(HttpContext context)
{
StringBuilder str = new StringBuilder();
str.Append("<html><head><title>hello, worldtitle>head><body><p>hello, world!p>body>html>");
context.HttpResponse.ResponseBody = Encoding.UTF8.GetBytes(str.ToString());
}
}
这个类中的ProcessRequest中只写了一个hello, world的html,并将其传给HttpResponse的响应体属性。
上面HttpApplication类中的HandleDynamicPage方法,就是用于获取请求动态页面所对应的的类对象,并调用该对象中的ProcessRequest方法,从而得到响应体。
Socket回发响应内容
这样Socket就可以通过HttpContext获取响应内容,并发送给浏览器了,其余的事情就交给浏览器了.
private void ProcessRequest(string str, Socket recieveSocket)
{
HttpContext context = new HttpContext(str);
HttpApplication application=new HttpApplication();
application.ProcessRequest(context);
recieveSocket.Send(context.HttpResponse.ResponseHeader);
recieveSocket.Send(context.HttpResponse.ResponseBody);
recieveSocket.Shutdown(SocketShutdown.Both);
recieveSocket.Close();
}
简单测试
打开简单的IIS服务器,然后点击里监听,在浏览器中输入http://192.168.1.100:9991/index.htm ,这里192.168.1.100是我本机的ip,9991是端口号,index.html是一个静态网页.
文本框中的内容就是浏览器的请求内容。
浏览器显示index.html
请求一个动态页面:http://192.168.1.100:9991/IndexPage.aspx
对于简单的页面可以进行测试成功,由于这是一个简单的模拟,主在理解浏览器的请求和服务器的响应方式,所以程序还是有不少需要改进的。
资源下载地址:http://download.csdn.net/detail/u012058778/9371404