因为 connection 对象仅仅跟 host 对象相关,且处理一个套接字,所以其数据成员仅有:
private Host _host; // 指向宿主对象
private Socket _socket; // 当前套接字
我们知道 host 调用且仅了 conn.ProcessOneRequest(); 方法,所以我们首先要找到此方法( coneetion 很多方法,我们先看看主要的):
public void ProcessOneRequest() { // wait for at least some input
if (WaitForRequestBytes() == 0) {
WriteErrorAndClose(400);
return;
}
Request request = new Request(_host, this);
request.Process();
}
从代码来看,此过程首先要确保 socket 有数据读入,如果发生无法读取,就向 socket 写入一个 400 错误;如果有数据读入,那么构造一个 requese 对象,调用 request 的 Process 方法(呵呵,麻烦事大家总是一层层甩给别人 J )。
还是继续分析 Connection 对象, WaitForRequestBytes() 实际上是单独线程来读取 socket ,如果 socket 连接但是没有数据流,就至多等待 10 秒。
WriteErrorAndClose(int); 函数调用 WriteErrorAndClose(int , string)
{
String body = Messages.FormatErrorMessageBody(statusCode, _host.VirtualPath);
if (message != null && message.Length > 0)
body += "\r\n
";
WriteEntireResponseFromString(statusCode, null, body, false);
}
WriteEntireResponseFromString() 函数根据状态码构造返回的 http 数据流,并写回到客户端。实际上体现了对 http 协议的具体实现。我们暂时放下,追踪看看 Request 对象。
internal class Request : SimpleWorkerRequest { 。。。。。。 } 继承自 .net ,根据 MSDN 介绍: HttpWorkerRequest 的这种简单实现提供请求 URL 和查询字符串,并将输出的正文捕获到 TextWriter 中。若要实现更为丰富的功能(如提供已发送的内容和标头以及捕获以二进制数据表示的响应标头或响应正文),则应扩展 SimpleWorkerRequest 并重写适当的 HttpWorkerRequest 方法。
还是从 Process 函数入手看看 ( 代码较长 ) :
{
ReadAllHeaders(); // 阅读处所有的 http 请求头
if (_headerBytes == null || _endHeadersOffset < 0 ||
_headerByteStrings == null || _headerByteStrings.Count == 0) {
_conn.WriteErrorAndClose(400);
return; // 如果读取 http 头错误就返回
}
ParseRequestLine(); // 处理 request 行输入
// Check for bad path
if (IsBadPath()) { // 防止用户请求 bad 路径
_conn.WriteErrorAndClose(400);
return;
}
// Limit to local requests only
if (!_conn.IsLocal) {
_conn.WriteErrorAndClose(403);
return;
}
// Check if the path is not well formed or is not for the current app
bool isClientScriptPath = false;
String clientScript = null;
if (!_host.IsVirtualPathInApp(_path, out isClientScriptPath, out clientScript)) {
_conn.WriteErrorAndClose(404); // 检验 url 请求是否属于应用程序路径范围内,如果不是,报 404 错误。
return;
}
ParseHeaders(); // 解析 http 请求头
ParsePostedContent(); // 解析 post 方法的内容
if (_verb == "POST" && _contentLength > 0 && _preloadedContentLength < _contentLength) { // 如果是 post 方法,需要等待 post 数据完成才继续那么调用 conn 的等待方法 Write100Continue 直到 post 完成
_conn.Write100Continue();
}
// special case for client script
if (isClientScriptPath) { // 如果请求的是脚本路径,那么直接读取文件(也就是 .js 文件,按照文本文件来看待)
_conn.WriteEntireResponseFromFile(_host.PhysicalClientScriptPath + clientScript, false);
return;
}
// special case for directory listing
if (ProcessDirectoryListingRequest()) { // 如果是请求目录 list ,则处理后返回
return;
}
PrepareResponse(); // 准备响应内容
// Hand the processing over to HttpRuntime
HttpRuntime.ProcessRequest(this); // 通过 HttpRuntime 的方法执行 asp.net 的内容,驱动所有 ASP.NET Web 处理执行。
}
针对该函数细节,逐个分析以下函数:
ReadAllHeaders
ParseRequestLine
ParsePostedContent
ProcessDirectoryListingRequest
PrepareResponse
因为他们处理一次 http request 。
private void ReadAllHeaders() {
_headerBytes = null;
do {
if (!TryReadAllHeaders())
break; // something bad happened
}
while (_endHeadersOffset < 0); // found \r\n\r\n
} 该函数不断调用 TryReadAllHeaders ,仔细看看这个 TryReadAllHeaders:
private bool TryReadAllHeaders() {
// read the first packet (up to 32K)
byte[] headerBytes = _conn.ReadRequestBytes(maxHeaderBytes); // 从 connection 读取最大数据 32*1024 字节。
if (headerBytes == null || headerBytes.Length == 0)
return false; // 如果读取数据失败,返回错误,调用此函数者应当检查数据读取是否完整
if (_headerBytes != null) { // previous partial read 以下将当前读取的数据累加
int len = headerBytes.Length + _headerBytes.Length;
if (len > maxHeaderBytes)
return false;
byte[] bytes = new byte[len];
// 注意调用了快速的 Buffer.BlockCopy 方法,不过我认为可以更好的处理读取数据问题,因为一再的产生 byte 数组显然不是很好的方法。
Buffer.BlockCopy(_headerBytes, 0, bytes, 0, _headerBytes.Length);
Buffer.BlockCopy(headerBytes, 0, bytes, _headerBytes.Length, headerBytes.Length);
_headerBytes = bytes;
}
else {
_headerBytes = headerBytes;
}
// start parsing 下面准备解析请求行
_startHeadersOffset = -1;
_endHeadersOffset = -1;
_headerByteStrings = new ArrayList();
// find the end of headers ByteParser 是自定义的工具类,此时我们只要知道是帮助字节数组转换方便得到行, ByteString 也是一个工具类,帮助将字节数组转换为字符串
ByteParser parser = new ByteParser(_headerBytes);
for (;;) {
ByteString line = parser.ReadLine();
if (line == null)
break;
if (_startHeadersOffset < 0) {
_startHeadersOffset = parser.CurrentOffset;
}
if (line.IsEmpty) {
_endHeadersOffset = parser.CurrentOffset;
break;
}
_headerByteStrings.Add(line);
}
return true;
}
如何处理分解行呢?
private void ParseRequestLine() {
ByteString requestLine = (ByteString)_headerByteStrings[0];
ByteString[] elems = requestLine.Split(' '); // 我们知道每一个 header 同 header 值之间都有一个必然的空格,例如 cassini 返回的 http 响应的头:
HTTP/1.1 404 Not Found
// 判断 header 是否读取正确,一般请求头第一行应该是例如: GET /pub/WWW/ HTTP/1.1
if (elems == null || elems.Length < 2 || elems.Length > 3) {
return;
}
_verb = elems[0].GetString(); // 读取 http 请求方法
ByteString urlBytes = elems[1];
_url = urlBytes.GetString(); // 获取请求的 url
if (elems.Length == 3) // 确定 http 请求的协议版本
_prot = elems[2].GetString(); // 目前仅有 HTTP/1.1 或者 HTTP/1.0
else
_prot = "HTTP/1.0";
// query string
int iqs = urlBytes.IndexOf('?'); // 请求的参数获取 字节数组表示
if (iqs > 0)
_queryStringBytes = urlBytes.Substring(iqs+1).GetBytes();
else
_queryStringBytes = new byte[0];
iqs = _url.IndexOf('?'); // 取得 path 和 参数的字符串表示
if (iqs > 0) {
_path = _url.Substring(0, iqs);
_queryString = _url.Substring(iqs+1);
}
else {
_path = _url;
_queryStringBytes = new byte[0];
}
// url-decode path 开始 url 解码,这个 MS 之前犯的著名 URL 解码错误就在此处了
if (_path.IndexOf('%') >= 0) {
_path = HttpUtility.UrlDecode(_path); // 调用 .net 的工具方法
}
// path info 以下获取 path
int lastDot = _path.LastIndexOf('.');
int lastSlh = _path.LastIndexOf('/');
if (lastDot >= 0 && lastSlh >= 0 && lastDot < lastSlh) {
int ipi = _path.IndexOf('/', lastDot);
_filePath = _path.Substring(0, ipi);
_pathInfo = _path.Substring(ipi);
}
else {
_filePath = _path;
_pathInfo = String.Empty;
}
_pathTranslated = MapPath(_filePath); // 映射路径,将文件映射到具体的磁盘路径
}
处理完 http header 后,开始处理 http 的请求正文,看看 ParsePostedContent
private void ParsePostedContent() {
_contentLength = 0;
_preloadedContentLength = 0;
String contentLengthValue = _knownRequestHeaders[HttpWorkerRequest.HeaderContentLength]; // 察看头部中的定义的长度
if (contentLengthValue != null) {
try {
_contentLength = Int32.Parse(contentLengthValue);
}
catch {
}
}
// 以下检查各个长度数据是否异常
if (_headerBytes.Length > _endHeadersOffset) {
_preloadedContentLength = _headerBytes.Length - _endHeadersOffset;
if (_preloadedContentLength > _contentLength && _contentLength > 0)
_preloadedContentLength = _contentLength; // don't read more than the content-length 注意不要读取过多的数据
n