最近在搬砖做web相关的东西。 因为要解除对一些框架的依赖, 所以有机会稍微接触一些底层的东西。
传统的web服务器包括IIS, tomcat, apache,Nginx都是基于http请求与响应来实现数据对接的。
在C#和java中都可以由httpwebRequest或者servlet相关的类或者包实现封装http的一些数据, 这样我们就不需要自己手动创建http格式的数据了。
而本文要透过这些类, 去看http到底包含了哪些东西, 然后封装属于自己的http响应数据,再通过socket发送给客户端(浏览器), 实现web服务器的一些简单的响应功能。
关于http协议的一些具体细节, 推荐这篇blog:HTTP协议详细
关于http我们要了解:
1.在TCP/IP体系结构中,HTTP属于应用层协议,位于TCP/IP协议的顶层。浏览Web时,浏览器通过HTTP协议与Web服务器交换信息。这些信息(文档)类型的格式由MIME定义。
2.HTTP按客户/服务器模式工作,HTTP是无状态的.
3.HTTP使用元信息作为头标。这里的元信息就是指那些用来指明http具体要做什么的数据,包括请求行或者报头等等。
UNLINK 取消两个资源之间的已有连接
5.Http常见的请求数据格式:
(1)请求数据:(最常用的两种请求是get跟post)
通常包括:请求行 + 请求报头 + 请求正文
请求行:动作(get等), URL(请求文件的位置), HTTP协议的版本号
请求报头:一些请求的具体信息。(这个可以自己看到的, 抓包)
请求正文: 注意请求正文与请求报头之间必须要有一个空行来区分。(2)响应数据:
通常:状态行 + 响应报头 + 响应正文
状态行:HTTP协议版本号 + 状态码 + 状态描述
这里常见状态码有:
200 (OK): 找到了该资源,并且一切正常。
304 (NOT MODIFIED): 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制。
401 (UNAUTHORIZED):客户端无权访问该资源。这通常会使得浏览器要求用户输入用户名和密码,以登录到服务器。
403 (FORBIDDEN):客户端未能获得授权。这通常是在401之后输入了不正确的用户名或密码。
404 (NOT FOUND):在指定的位置不存在所申请的资源。
响应报头: 响应的一些格式。通常包含传输数据的格式,数据长度等等。
响应正文:要返回给客户端的数据, 这里也注意与响应报头留有空行。
有了这些格式, 我们就可以通过C#构建自己的WEB服务器了。
需要用到socket用来传输数据, 当接受到浏览器端传来的数据时, 我们解析这个http协议, 获取一些需要知道的信息, 比如是什么类型的请求, 需要请求服务器上什么物理位置的文件等。 然后我们按照http的标准协议封装响应数据的三个部分, 发送给浏览器。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
namespace WebServer
{
class MyWebServer
{
private readonly string webServerRoot = @"E:\Pages\";
private readonly int port = 4096;
private readonly string host = "127.0.0.1";
private Socket socket = null;
public MyWebServer()
{
try
{
IPAddress ip = IPAddress.Parse(host);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(ip, port));
socket.Listen(10);
Thread th = new Thread(new ThreadStart(Listen));
th.Start();
Console.WriteLine("Server Is Listening ...");
}
catch(Exception e)
{
Console.WriteLine("Fail When Listening : " + e.ToString());
}
}
public void SendHttpHead(string httpVersion, string statusCode, string mime, int codeLength, ref Socket socket)
{
string sendBuffer = "";
sendBuffer += httpVersion + statusCode + "\r\n";
sendBuffer += "Query Ok In May GMT";
if (mime.Length == 0)
{
mime = "text/html";
}
sendBuffer += "Content-Type: " + mime + "\r\n";
sendBuffer += "Accept-Ranges: bytes\r\n";
sendBuffer += "Content-Length: " + codeLength + "\r\n\r\n";
Byte[] sendDataByte = Encoding.ASCII.GetBytes(sendBuffer);
SendToBrowser(sendDataByte, ref socket);
// SendToBrowser(sendBuffer, socket);
}
public void SendToBrowserByString(string sendData, ref Socket socket)
{
SendToBrowser(Encoding.ASCII.GetBytes(sendData), ref socket);
}
public void SendToBrowser(Byte[] sendData, ref Socket socket)
{
try
{
if(socket.Connected)
{
int dataNumber = socket.Send(sendData);
Console.WriteLine("Send Bytes {0}", dataNumber);
}
else
{
Console.WriteLine("Fail in Connecting ...");
}
}
catch(Exception e)
{
Console.WriteLine("Error !");
}
}
public void Listen()
{
string httpVersion, statusCode, mime;
int dataLength;
while(true)
{
Socket mySocket = socket.Accept();
if(mySocket.Connected)
{
Byte[] dataFromBrow = new Byte[100];
mySocket.Receive(dataFromBrow, dataFromBrow.Length, 0);
string receiveBuf = Encoding.ASCII.GetString(dataFromBrow);
Console.WriteLine(receiveBuf);
if(receiveBuf.Substring(0, 3) != "GET")
{
Console.WriteLine("sorry, we just can accept the \"get\" request");
mySocket.Close();
return;
}
int startPos = receiveBuf.IndexOf("HTTP", 1);
httpVersion = receiveBuf.Substring(startPos, 8);//get HttpVersion
string subString = receiveBuf.Substring(0, startPos - 1);
startPos = subString.LastIndexOf("/") + 1;
string requestFile = subString.Substring(startPos);//get the FileName
string localDir = webServerRoot;
if(requestFile.Length == 0)
{
requestFile = "Index.html";
}
string requestFilePath = localDir + requestFile;///get the definite path of the request file
if(File.Exists(requestFilePath) == false)
{
string errorMessage = "404 Error! File Does Not Exists";
SendHttpHead(httpVersion," 404 Not Found", "", errorMessage.Length, ref mySocket);
SendToBrowserByString("Error", ref mySocket);
// Console.WriteLine("Error : 404");
}
else
{
dataLength = 0;
string responseData = "";
FileStream fs = new FileStream(requestFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
BinaryReader reader = new BinaryReader(fs);
byte[] bytes = new byte[fs.Length];
int read;
while ((read = reader.Read(bytes, 0, bytes.Length)) != 0)
{
responseData = responseData + Encoding.ASCII.GetString(bytes, 0, read);
dataLength += read;
}
reader.Close();
fs.Close();
mime = "text/html";
statusCode = " 200 OK";
SendHttpHead(httpVersion, mime, statusCode, dataLength, ref mySocket);//200 OK
SendToBrowserByString(responseData, ref mySocket);
}
mySocket.Close();
}
}
}
}
class Program
{
static void Main(string[] args)
{
MyWebServer myWebServer = new MyWebServer();
Console.ReadKey();
}
}
}