Asp.net学习第一天---OurHttpServer
今天开始学习ASP.NET,这个对于我来说是个非常陌生的玩意,当然要从基本的开始学起了.晚上把今天的学习知识整理了下.
--我们到底是怎么看到网站的?
当我们在浏览器的地址栏里输入地址,敲回车后,浏览器到底做什么一些什么事情了?用户在浏览器地址栏输入网址www.baidu.com,浏览器先看当前电脑上是否保存了www.baidu.com对应的IP地址如果有,就直接请求了;如果没有,则是先到dns服务器查询,dns返回查询到的IP地址给浏览器,浏览器在本机保存,并发送请求到对应的IP地址.当我们的请求到达服务器时,服务器会对我们的请求做出响应.给我们的响应中可以是一个静态网页,也可以是动态页面.浏览器只负责解释执行html+css+javascript代码,服务器可以执行的服务端语言:c#,java...分别由不同的运行环境执行代码(framework,jvm).
-静态页面:在服务器就相当于直接读取文件字符串然后返回客户端浏览器
-动态页面:在服务器是先交给某语言环境虚拟机编译运行的,按照语法生成代码返回给客户端
--服务器与浏览器之间的交互用什么方式了?
浏览器跟服务器本质上就是两个使用socket进行基于http协议通信的两个应用程序(就相当于两个用电话说中文进行交流).
--写一个简单的httpserver
自己做个服务器程序要解决很多问题:
- 怎么接收浏览器的请求?接收以后怎么理解?怎么向浏览器发送响应,发送后浏览器怎么理解
- 如何对于静态页面(html/css/javascript)的请求做出响应
- 如何对动态页面(ashx/aspx)的请求做出响应,动态文件可是有很多的,使用什么方式来调用呢?
- 如何对图片文件(jpg/gif)的请求做出响应?
解决思路:
- 使用套接字进行数据交换。
- 使用HTTP协议(到底什么是HTTP协议?)的数据,双方按照此协议解释理解接收到的数据。
- 直接读取静态文件的数据并通过套接字发送回浏览器。
- 对于动态文件的请求,可以通过分析请求页面,并反射所对应的类的对象,并通过接口调用类的方法。
下面开始模拟一个IIS服务器吧(只是单纯模拟,帮助自己学习asp.net的)
- 首先我们先监听浏览器的连接,这里服务器我们就用winform吧
1 private void btnStart_Click(object sender, EventArgs e) 2 { 3 //服务端的IP 4 IPAddress ipAddress = IPAddress.Parse(this.txtIP.Text); 5 //端口 6 IPEndPoint endPoint = new IPEndPoint(ipAddress,int.Parse(txtPort.Text)); 7 //开启一个监听的socket 8 Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp); 9 //绑定 10 socket.Bind(endPoint); 11 //开启监听 12 socket.Listen(10); 13 CurrentServierSocket = socket; 14 this.lbStatus.Text = "已经启动.."; 15 //由于接收用户请求会阻塞当前的进程,所以我们开启另外一个线程帮我们接收用户,由于要不断的接收,我们就用死循环 16 ThreadPool.QueueUserWorkItem(a => 17 { 18 19 Socket serverSocket = (Socket) a; 20 while (true) 21 { 22 //接收用户 23 var proxSocket = socket.Accept(); 24 //开启另外一个线程不断的接收用户的请求,由于也要不断请求,不断回应(请求后),我们这里放在死循环里,这么写会出现很多问题,只是单纯的模拟也就不用try住了 25 26 ThreadPool.QueueUserWorkItem(s => 27 { 28 Socket pSocket = (Socket)s; 29 byte[] data = new byte[1024 * 1024]; 30 int realLength = pSocket.Receive(data, 0, data.Length, 0); 31 string requestStr = Encoding.UTF8.GetString(data, 0, realLength); 32 //响应用户的请求 33 ProcessRequest(requestStr,pSocket); 34 }, proxSocket); 35 } 36 }, socket); 37 } 38 /// <summary> 39 /// 响应请求 40 /// </summary> 41 /// <param name="requestStr"></param> 42 /// <param name="socket"></param> 43 public void ProcessRequest(string requestStr,Socket socket) 44 { 45 if(string.IsNullOrEmpty(requestStr)) 46 { 47 return; 48 } 49 this.richTextBox1.Text +="\r\n" + requestStr; 50 HttpApplication application = new HttpApplication(); 51 52 HttpContext context = new HttpContext(requestStr); 53 54 application.ProcessRequest(context); 55 56 //CurrentServierSocket.Send() 57 byte[] header = context.Response.GetHeader(); 58 socket.Send(header, 0, header.Length, 0); 59 60 byte[] responseBody = context.Response.GetData(); 61 socket.Send(responseBody, 0, responseBody.Length, 0); 62 63 socket.Shutdown(SocketShutdown.Both); 64 65 socket.Close(); 66 } 67 }
- ok,当我们开始监听客户端发回来的请求后,我们下面就要对发过来的请求做出响应,首先我们需要一个HttpContext类来帮我们存放请求报文跟响应报文
我们添加一个HttpContext和httpresponse跟httprequest类来:
public class HttpContext { public HttpRequest Request { get; set; } public HttpResponse Response { get; set; } public HttpContext(string requestTxt) { this.Request = new HttpRequest(requestTxt); this.Response = new HttpResponse(this.Request); } }
在httprequest类中我们需要对请求的报文进行解析:具体怎么做如下(对于请求的报文,我只处理了一部分,只是模拟)
public class HttpRequest { //请求页面地址 public string RequestURL { get; set; } //请求行 public string RawRequestTxt { get; set; } //请求方法:post,get等 public string Method { get; set; } public HttpRequest(string requestStr) { //处理 设置当前的请求的url string temp = requestStr.Replace("\r\n", "\r"); string[] allLines = temp.Split('\r'); string requestLine = allLines[0]; RequestURL = requestLine.Split(' ')[1]; //设置当前的RawRequestText this.RawRequestTxt = requestStr; //设置Method Method = requestLine.Split(' ')[0]; } }
在httpresponse类中我们需要根据httprequest来做出响应体的构造,具体如下:
public class HttpResponse { public byte[] ResponseData { get; set; } public string StatusCode { get; set; } public Dictionary<string,string> StatusDic { get; set; } public string RequestURL = string.Empty; /* HTTP/1.1 200 OK -------------------------statuscode statusdic Content-Type: text/html Last-Modified: Tue, 17 Jul 2012 07:36:38 GMT Accept-Ranges: bytes ETag: "0741e6ee63cd1:0" Server: Microsoft-IIS/7.5 X-Powered-By: ASP.NET Date: Tue, 17 Jul 2012 14:45:55 GMT Content-Length: 70*/ public byte[] GetHeader() { StringBuilder sb = new StringBuilder(); sb.AppendFormat("HTTP/1.1 200 OK\r\n"); sb.AppendFormat("Content-Type: {0}\r\n", GetContentType(RequestURL)); if (this.ResponseData != null) { sb.AppendFormat("Content-Length: {0}\r\n\r\n", this.ResponseData.Length); } return Encoding.UTF8.GetBytes(sb.ToString()); } /// <summary> /// 根据请求的url定义contentype /// </summary> /// <param name="RequestURL"></param> /// <returns></returns> public string GetContentType(string RequestURL) { string ext = Path.GetExtension(RequestURL); string type = "text/html"; switch (ext) { case ".jpg" : type = "image/jpeg"; break; case ".png": type = "image/png"; break; case ".gif": type = "image/gif"; break; case ".js": type = "application/x-javascript"; break; case ".css": type = "text/css"; break; case ".htm": case ".html": type = "text/html"; break; case ".aspx": type = "text/html"; break; default: type = "text/plain"; break; } return type; } public byte[] GetData() { return ResponseData; } /// <summary> /// ctor /// </summary> /// <param name="request"></param> public HttpResponse(HttpRequest request) { StatusDic = new Dictionary<string, string>(); StatusDic.Add("200","Ok"); StatusDic.Add("404", "Object Not Found"); RequestURL = request.RequestURL; } }
我们还需要一个HttpApplication类来帮我们获得数据,里面包含三个方法,一个处理上下文httpcontext,一个用来处理静态页面请求,一个是动态.
public class HttpApplication :IHttpHandler { public void ProcessRequest(HttpContext context) { //获取基目录 string currentExcutePath = AppDomain.CurrentDomain.BaseDirectory; //获取全路径 string fileName = Path.Combine(currentExcutePath, context.Request.RequestURL.TrimStart('/')); //获取后缀名 string strFileExt = Path.GetExtension(fileName); switch (strFileExt) { case ".html": case ".htm": case ".jpg": case ".gif": case ".png": case ".js": case ".css": //处理静态页面 ProcessRequestStaticFile(fileName,context); break; case ".aspx": //处理动态页面 ProcessDnamicPage(fileName,context); break; default: //默认 ProcessRequestStaticFile(fileName,context); break; } } /// <summary> /// 动态 /// </summary> /// <param name="fileName"></param> /// <param name="context"></param> private void ProcessDnamicPage(string fileName,HttpContext context) { //获取除去扩展名的文件名 string className = Path.GetFileNameWithoutExtension(fileName); //获取当前执行改方法的程序集 var assembly = MethodBase.GetCurrentMethod().DeclaringType.Assembly; //获取命名控件加类名的全名称 string fullName = MethodBase.GetCurrentMethod().DeclaringType.Namespace + "." + className; //因为可能请求动态的页面不只是一种,这里我们用接口来统一调用,利用反射穿件一个实例 IHttpHandler handler = (IHttpHandler)assembly.CreateInstance(fullName, true); if(handler == null) { string path = AppDomain.CurrentDomain.BaseDirectory + "web/404.htm"; context.Response.StatusCode = "404"; context.Response.ResponseData = File.ReadAllBytes(path); } else { handler.ProcessRequest(context); context.Response.StatusCode = "200"; } //return context.Response.ResponseData; } private void ProcessRequestStaticFile(string fileName,HttpContext context) { if(File.Exists(fileName) ) { context.Response.StatusCode = "200"; context.Response.ResponseData = File.ReadAllBytes(fileName); } else { string path = AppDomain.CurrentDomain.BaseDirectory + "web/404.htm"; context.Response.StatusCode = "404"; //如果是静态的直接读取了字符串 context.Response.ResponseData = File.ReadAllBytes(path); } } }
接口和一个动态类(我也写死了,并没求通过用户传值过来,然后去数据库取值等操作,同样是模拟,少了很多操作)
public class Login : IHttpHandler { public string PageName { get; set; } public void ProcessRequest(HttpContext context) { string str = @" <html> <head></head> <body> <h1>hello the shit! 你大爷的...</h1> </body> </html>"; context.Response.ResponseData = Encoding.UTF8.GetBytes(str); } }
public interface IHttpHandler { void ProcessRequest(HttpContext context); }
总结:这是个小小的demo 目的是帮助我了解一个初步的asp.net运行机制,但是asp.net的运行机制可比这个要复杂的多了,明天继续学习.第一次写,写的很烂,大家给点意见!