CASSINI源代码分析(4)
因为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<!--\r\n" + message + "\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 注意不要读取过多的数据
_preloadedContent = new byte[_preloadedContentLength];
Buffer.BlockCopy(_headerBytes, _endHeadersOffset, _preloadedContent, 0, _preloadedContentLength); //拷贝数据
}
}
以上将http请求的content数据字节拷贝到_preloadedContent成员变量中去。
接下来的ProcessDirectoryListingRequest处理不带文件名的请求,也就是直接请求浏览某个目录。略。
处理完毕后,准备构造响应数据,看看PrepareResponse
_headersSent = false; //准备回写到connection中去
_responseStatus = 200;
_responseHeadersBuilder = new StringBuilder();
_responseBodyBytes = new ArrayList();
其他的,我们应当注意
HttpRuntime.ProcessRequest(this); 隐含的调用关系。HttpRuntime.ProcessRequest会在需要回写数据的时候调用相关的函数,这些函数被cassini注释。
我们要明白这些重载的函数会在asp.net处理过程中被调用。
///////////////////////////////////////////////////////////////////////////////////////////////
//
// Implementation of HttpWorkerRequest
//
///////////////////////////////////////////////////////////////////////////////////////////////
public override String GetUriPath() { //返回请求的 URI 的虚拟路径。
return _path;
}
public override String GetQueryString() { //返回请求 URL 中指定的查询字符串。
return _queryString;
}
public override byte[] GetQueryStringRawBytes() { //在派生类中被重写时,以字节数组的形式返回响应查询字符串。
return _queryStringBytes;
}
public override String GetRawUrl() { //返回附加了查询字符串的请求标头中包含的 URL 路径。
return _url;
}
public override String GetHttpVerbName() { //返回请求标头的指定成员。
return _verb;
}
public override String GetHttpVersion() { //提供对请求的 HTTP 版本(如“HTTP/1.1”)的访问。
return _prot;
}
public override String GetRemoteAddress() {//提供对请求标头的指定成员的访问。
return _conn.RemoteIP;
}
public override int GetRemotePort() {//提供对请求标头的指定成员的访问。
return 0;
}
public override String GetLocalAddress() {//
return _conn.LocalIP;
}
public override int GetLocalPort() {
return _host.Port;
}
public override String GetFilePath() {//在派生类中被重写时,返回所请求的 URI 的物理路径。
return _filePath;
}
public override String GetFilePathTranslated() {//返回请求的 URI 的物理文件路径(并将其从虚拟路径翻译成物理路径:例如,从“/proj1/page.aspx”翻译成“c:\dir\page.aspx”)
return _pathTranslated;
}
public override String GetPathInfo() {//返回具有 URL 扩展的资源的其他路径信息。即对于路径/virdir/page.html/tail,GetPathInfo 值为/tail。
return _pathInfo;
}
public override String GetAppPath() {//返回当前正在执行的服务器应用程序的虚拟路径。
return _host.VirtualPath;
}
public override String GetAppPathTranslated() {//返回当前正在执行的服务器应用程序的 UNC 翻译路径。
return _host.PhysicalPath;
}
public override byte[] GetPreloadedEntityBody() {//返回 HTTP 请求正文已被读取的部分。
return _preloadedContent;
}
public override bool IsEntireEntityBodyIsPreloaded() {//返回一个值,该值指示是否所有请求数据都可用,以及是否不需要对客户端进行进一步读取。
return (_contentLength == _preloadedContentLength);
}
public override int ReadEntityBody(byte[] buffer, int size) {//读取客户端的请求数据(在尚未预加载时)。
int bytesRead = 0;
byte[] bytes = _conn.ReadRequestBytes(size);
if (bytes != null && bytes.Length > 0) {
bytesRead = bytes.Length;
Buffer.BlockCopy(bytes, 0, buffer, 0, bytesRead);
}
return bytesRead;
}
public override String GetKnownRequestHeader(int index) {//返回与指定的索引相对应的标准 HTTP 请求标头。
return _knownRequestHeaders[index];
}
public override String GetUnknownRequestHeader(String name) {//返回非标准的 HTTP 请求标头值。指定了名称
int n = _unknownRequestHeaders.Length;
for (int i = 0; i < n; i++) {
if (String.Compare(name, _unknownRequestHeaders[i][0], true, CultureInfo.InvariantCulture) == 0)
return _unknownRequestHeaders[i][1];
}
return null;
}
public override String[][] GetUnknownRequestHeaders() {//获取所有非标准的 HTTP 标头的名称-值对。
return _unknownRequestHeaders;
}
public override String GetServerVariable(String name) {//从与请求关联的服务器变量词典返回单个服务器变量。
String s = String.Empty;
switch (name) {
case "ALL_RAW":
s = _allRawHeaders;
break;
case "SERVER_PROTOCOL":
s = _prot;
break;
// more needed?
}
return s;
}
public override String MapPath(String path) {//返回与指定虚拟路径相对应的物理路径。
String mappedPath = String.Empty;
if (path == null || path.Length == 0 || path.Equals("/")) {
// asking for the site root
if (_host.VirtualPath == "/") {
// app at the site root
mappedPath = _host.PhysicalPath;
}
else {
// unknown site root - don't point to app root to avoid double config inclusion
mappedPath = Environment.SystemDirectory;
}
}
else if (_host.IsVirtualPathAppPath(path)) {
// application path
mappedPath = _host.PhysicalPath;
}
else if (_host.IsVirtualPathInApp(path)) {
// inside app but not the app path itself
mappedPath = _host.PhysicalPath + path.Substring(_host.NormalizedVirtualPath.Length);
}
else {
// outside of app -- make relative to app path
if (path.StartsWith("/"))
mappedPath = _host.PhysicalPath + path.Substring(1);
else
mappedPath = _host.PhysicalPath + path;
}
mappedPath = mappedPath.Replace('/', '\\');
if (mappedPath.EndsWith("\\") && !mappedPath.EndsWith(":\\"))
mappedPath = mappedPath.Substring(0, mappedPath.Length-1);
return mappedPath;
}
public override void SendStatus(int statusCode, String statusDescription) {//指定响应的 HTTP 状态代码和状态说明;例如 SendStatus(200, "Ok")。
_responseStatus = statusCode;
}
public override void SendKnownResponseHeader(int index, String value) {//将标准 HTTP 标头添加到响应。
if (_headersSent)
return;
switch (index) {
case HttpWorkerRequest.HeaderServer:
case HttpWorkerRequest.HeaderDate:
case HttpWorkerRequest.HeaderConnection:
// ignore these
return;
// special case headers for static file responses
case HttpWorkerRequest.HeaderAcceptRanges:
if (value == "bytes") {
_specialCaseStaticFileHeaders = true;
return;
}
break;
case HttpWorkerRequest.HeaderExpires:
case HttpWorkerRequest.HeaderLastModified:
if (_specialCaseStaticFileHeaders)
return;
break;
}
_responseHeadersBuilder.Append(GetKnownResponseHeaderName(index));
_responseHeadersBuilder.Append(": ");
_responseHeadersBuilder.Append(value);
_responseHeadersBuilder.Append("\r\n");
}
public override void SendUnknownResponseHeader(String name, String value) {//将非标准 HTTP 标头添加到响应。
if (_headersSent)
return;
_responseHeadersBuilder.Append(name);
_responseHeadersBuilder.Append(": ");
_responseHeadersBuilder.Append(value);
_responseHeadersBuilder.Append("\r\n");
}
public override void SendCalculatedContentLength(int contentLength) {//将 Content-Length HTTP 标头添加到响应。
if (!_headersSent) {
_responseHeadersBuilder.Append("Content-Length: ");
_responseHeadersBuilder.Append(contentLength.ToString());
_responseHeadersBuilder.Append("\r\n");
}
}
public override bool HeadersSent() {//返回一个值,该值指示是否已为当前的请求将 HTTP 响应标头发送到客户端。
return _headersSent;
}
public override bool IsClientConnected() {//返回一个值,该值指示客户端连接是否仍处于活动状态。
return _conn.Connected;
}
public override void CloseConnection() {//终止与客户端的连接。
_conn.Close();
}
public override void SendResponseFromMemory(byte[] data, int length) {//将内存块的内容添加到响应。
if (length > 0) {
byte[] bytes = new byte[length];
Buffer.BlockCopy(data, 0, bytes, 0, length);
_responseBodyBytes.Add(bytes);
}
}
public override void SendResponseFromFile(String filename, long offset, long length) {//将文件的内容添加到响应。
if (length == 0)
return;
FileStream f = null;
try {
f = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
SendResponseFromFileStream(f, offset, length);
}
finally {
if (f != null)
f.Close();
}
}
public override void SendResponseFromFile(IntPtr handle, long offset, long length) {//将文件的内容添加到响应。注意这个方法是多态的
if (length == 0)
return;
FileStream f = null;
try {
f = new FileStream(handle, FileAccess.Read, false);
SendResponseFromFileStream(f, offset, length);
}
finally {
if (f != null)
f.Close();
}
}
private void SendResponseFromFileStream(FileStream f, long offset, long length) {//这个可不是重载的,看清楚了J
const int maxChunkLength = 64*1024; //64K作为传输块
long fileSize = f.Length;
if (length == -1)
length = fileSize - offset;
if (length == 0 || offset < 0 || length > fileSize - offset)
return;
if (offset > 0)
f.Seek(offset, SeekOrigin.Begin);
if (length <= maxChunkLength) {
byte[] fileBytes = new byte[(int)length];
int bytesRead = f.Read(fileBytes, 0, (int)length);
SendResponseFromMemory(fileBytes, bytesRead);
}
else {
byte[] chunk = new byte[maxChunkLength];
int bytesRemaining = (int)length;
while (bytesRemaining > 0) {
int bytesToRead = (bytesRemaining < maxChunkLength) ? bytesRemaining : maxChunkLength;
int bytesRead = f.Read(chunk, 0, bytesToRead);
SendResponseFromMemory(chunk, bytesRead);
bytesRemaining -= bytesRead;
// flush to release keep memory
if (bytesRemaining > 0 && bytesRead > 0)
FlushResponse(false);
}
}
}
public override void FlushResponse(bool finalFlush) {//将所有挂起的响应数据发送到客户端。
if (!_headersSent) {
_conn.WriteHeaders(_responseStatus, _responseHeadersBuilder.ToString());
_headersSent = true;
}
for (int i = 0; i < _responseBodyBytes.Count; i++) {
byte[] bytes = (byte[])_responseBodyBytes[i];
_conn.WriteBody(bytes, 0, bytes.Length);
}
_responseBodyBytes = new ArrayList();
if (finalFlush) {
_conn.Close();
}
}
public override void EndOfRequest() {//由运行库使用以通知 HttpWorkerRequest 当前请求的请求处理已完成。
// empty method
}
通过以上重载,我们实现了具体的http请求的数据获取、头分析,内容分析(如果是post方法)、querystring参数分析、处理路径映射、传输asp.net执行请求到运行库,同时构造http 响应头和相应数据。