FastCgiNet
用C#编写的FastCGI库。 FASTCGI是一项协议,允许传统的CGI应用程序或控制台应用程序与Web服务器并肩无效(无需更改其代码),从而响应用户的HTTP请求。它也可用于运行其他类型的Web应用程序(PHP,ROR和OWIN应用程序),并且出于多种原因是一个很好的托管选择(请参阅官方网站)。应该注意的是, FastCgiNet不打算在没有任何代码更改的情况下启用Console .NET应用程序运行: FastCgiNet不会重定向stdin,stdout或stderr。它为应用程序提供了其他机制,可以用作CGI应用程序。
API和安装
您可以克隆并使用Monodevelops或Visual Studio构建,也可以通过Nuget下载FastCgiNet 。提防API可能会在不久的将来发生变化,尽管并不是很糟糕。也就是说,这里有一些有关如何使用此库的文档:
请求API
FastCgiNet中有两个API:记录API和请求API。请求API是推荐的API,应满足大多数用户的需求。本节描述了它。
Web服务器的观点:
- 当浏览器请求页面/URL时,Web服务器必须从应用程序请求答案。
- 然后,应用程序将编写HTTP响应状态,HTTP响应标头和标准输出(也可以写入标准错误输出),Web服务器最终将其发送给实际访问者。
因此,如果您正在编写Web服务器,则可能需要每个访问者的请求使用WeberverSocketRequest Request,例如:
FastCgiNet;
using FastCgiNet .Streams;
using FastCgiNet .Requests;
…
// Let\’s simulate a GET request to http://gi*thub.**com/mzabani/FastCgiNet
var requestedUrl = new Uri(\”http://gi*thub.**com/mzabani/FastCgiNet\”);
string requestMethod = \”GET\”;
// Suppose the FastCgi application is listening on 127.0.0.1, port 9000
var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.Connect(new IPEndPoint(IPAddress.Loopback, 9000));
// There must be no two concurrent requests with the same requestid, even if in different sockets. For simplicity, this request will have request id equal to 1
ushort requestId = 1;
using (var request = new WebServerSocketRequest(sock, requestId))
{
// The BeginRequest Record defines how the application should respond. To know more read FastCgi\’s docs.
request.SendBeginRequest(Role.Responder, true);
// The Request Headers are sent with Params Records. You don\’t have to worry about the mechanisms, though: just write to the Params stream.
using (var nvpWriter = new NvpWriter(request.Params))
{
// The WriteParamsFromUri is a helper method that writes the following Name-Value Pairs:
// HTTP_HOST, HTTPS, SCRIPT_NAME, DOCUMENT_URI, REQUEST_METHOD, SERVER_NAME, QUERY_STRING, REQUEST_URI, SERVER_PROTOCOL, GATEWAY_INTERFACE
nvpWriter.WriteParamsFromUri(requestedUrl, requestMethod);
// The other http request headers, e.g. User-Agent
nvpWriter.Write(\”HTTP_USER_AGENT\”, \”Super cool Browser v1.0\”);
}
// If there is any request body, send it through the Stdin stream. If there is nothing to send, send an End-Of-Request Record (an empty record)
request.SendEmptyStdin();
// At this point, the application is processing the request and cooking up a response for us, so let\’s welcome the incoming data until the response is over
int bytesRead;
byte[] buf = new byte[4096];
while (!request.ResponseComplete)
{
bytesRead = sock.Receive(buf, SocketFlags.None);
request.FeedBytes(buf, 0, bytesRead);
}
// All the application\’s response will be in the Stdout and/or Stderr streams
// Don\’t forget that the very first line of the output is ASCII encoded text with the response status, such as \”Status: 200 OK\”
using (var reader = new StreamReader(request.Stdout))
{
Console.Write(reader.ReadToEnd());
}
}
// The socket and all other resources are automatically disposed at this point.
// This implies that WebServerSocketRequest still doesn\’t multiplex requests
// (not for long, hopefully – also, you can inherit from this class and make
// Dispose() not call CloseSocket() if you want to multiplex requests)
\”>
using FastCgiNet ; using FastCgiNet . Streams ; using FastCgiNet . Requests ; .. . // Let\'s simulate a GET request to http://gi*thub.**com/mzabani/FastCgiNet var requestedUrl = new Uri ( \"http://gi*thub.**com/mzabani/FastCgiNet\" ) ; string requestMethod = \"GET\" ; // Suppose the FastCgi application is listening on 127.0.0.1, port 9000 var sock = new Socket ( AddressFamily . InterNetwork , SocketType . Stream , ProtocolType . Tcp ) ; sock . Connect ( new IPEndPoint ( IPAddress . Loopback , 9000 ) ) ; // There must be no two concurrent requests with the same requestid, even if in different sockets. For simplicity, this request will have request id equal to 1 ushort requestId = 1 ; using ( var request = new WebServerSocketRequest ( sock , requestId ) ) { // The BeginRequest Record defines how the application should respond. To know more read FastCgi\'s docs. request . SendBeginRequest ( Role . Responder , true ) ; // The Request Headers are sent with Params Records. You don\'t have to worry about the mechanisms, though: just write to the Params stream. using ( var nvpWriter = new NvpWriter ( request . Params ) ) { // The WriteParamsFromUri is a helper method that writes the following Name-Value Pairs: // HTTP_HOST, HTTPS, SCRIPT_NAME, DOCUMENT_URI, REQUEST_METHOD, SERVER_NAME, QUERY_STRING, REQUEST_URI, SERVER_PROTOCOL, GATEWAY_INTERFACE nvpWriter . WriteParamsFromUri ( requestedUrl , requestMethod ) ; // The other http request headers, e.g. User-Agent nvpWriter . Write ( \"HTTP_USER_AGENT\" , \"Super cool Browser v1.0\" ) ; } // If there is any request body, send it through the Stdin stream. If there is nothing to send, send an End-Of-Request Record (an empty record) request . SendEmptyStdin ( ) ; // At this point, the application is processing the request and cooking up a response for us, so let\'s welcome the incoming data until the response is over int bytesRead ; byte [ ] buf = new byte [ 4096 ] ; while ( ! request . ResponseComplete ) { bytesRead = sock . Receive ( buf , SocketFlags . None ) ; request . FeedBytes ( buf , 0 , bytesRead ) ; } // All the application\'s response will be in the Stdout and/or Stderr streams // Don\'t forget that the very first line of the output is ASCII encoded text with the response status, such as \"Status: 200 OK\" using ( var reader = new StreamReader ( request . Stdout ) ) { Console . Write ( reader . ReadToEnd ( ) ) ; } } // The socket and all other resources are automatically disposed at this point. // This implies that WebServerSocketRequest still doesn\'t multiplex requests // (not for long, hopefully - also, you can inherit from this class and make // Dispose() not call CloseSocket() if you want to multiplex requests)
上面的代码示例很好地证明了使用FastCgiNet容易性!但是,不要忘记处理各种错误。例如,该应用程序可以随时突然关闭插座,如果邪恶的应用程序永远不会发送EndRequest记录,则邪恶的应用程序可能会使您进入无限循环。现在,让我们从应用程序的角度看一下它,并使用applicationsocketRequest类:
FastCgiNet;
using FastCgiNet .Streams;
using FastCgiNet .Requests;
…
using (var listenSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
listenSock.Bind(new IPEndPoint(IPAddress.Loopback, 9000));
listenSock.Listen(1);
// For simplicity, let\’s accept only one connection
var sock = listenSock.Accept();
using (var request = new ApplicationSocketRequest(sock))
{
// Now let\’s wait until we have received the Params and Stdin streams completely
int bytesRead;
byte[] buf = new byte[4096];
while (!request.Params.IsComplete || !request.Stdin.IsComplete)
{
bytesRead = sock.Receive(buf, SocketFlags.None);
request.FeedBytes(buf, 0, bytesRead);
}
// Let\’s look for the requested path and ignore everything else
string requestedPath = null;
using (var nvpReader = new NvpReader(request.Params))
{
NameValuePair nvp;
while ((nvp = nvpReader.Read()) != null)
{
if (nvp.Name == \”DOCUMENT_URI\”)
requestedPath = nvp.Value;
}
}
// Let\’s write a classic response
using (var writer = new StreamWriter(request.Stdout))
{
// The headers first
writer.NewLine = \”\\r\\n\”;
writer.Write(\”Status: 200 OK\”);
writer.WriteLine(\”Content-Type: text/html\”);
writer.WriteLine();
// Now the body
writer.Write(\”<html><head><title>Hello World</title></head><body><h1>Hello FastCgiNet !</h1>The requested path was {0}</body></html>\”, requestedPath);
}
// Our application status and end of request. The FastCgi Standard defines that returning 0 indicates there were no errors
request.SendEndRequest(0, ProtocolStatus.RequestComplete);
}
// The connection socket and all other resources (except for the
// listen socket) are automatically disposed at this point. This
// implies that ApplicationSocketRequest still doesn\’t multiplex
// requests (not for long, hopefully – also, you can inherit from
// this class and make Dispose() not call CloseSocket() if
// you want to multiplex requests).
}
\”>
using FastCgiNet ; using FastCgiNet . Streams ; using FastCgiNet . Requests ; .. . using ( var listenSock = new Socket ( AddressFamily . InterNetwork , SocketType . Stream , ProtocolType . Tcp ) ) { listenSock . Bind ( new IPEndPoint ( IPAddress . Loopback , 9000 ) ) ; listenSock . Listen ( 1 ) ; // For simplicity, let\'s accept only one connection var sock = listenSock . Accept ( ) ; using ( var request = new ApplicationSocketRequest ( sock ) ) { // Now let\'s wait until we have received the Params and Stdin streams completely int bytesRead ; byte [ ] buf = new byte [ 4096 ] ; while ( ! request . Params . IsComplete || ! request . Stdin . IsComplete ) { bytesRead = sock . Receive ( buf , SocketFlags . None ) ; request . FeedBytes ( buf , 0 , bytesRead ) ; } // Let\'s look for the requested path and ignore everything else string requestedPath = null ; using ( var nvpReader = new NvpReader ( request . Params ) ) { NameValuePair nvp ; while ( ( nvp = nvpReader . Read ( ) ) != null ) { if ( nvp . Name == \"DOCUMENT_URI\" ) requestedPath = nvp . Value ; } } // Let\'s write a classic response using ( var writer = new StreamWriter ( request . Stdout ) ) { // The headers first writer . NewLine = \" \\r \\n \" ; writer . Write ( \"Status: 200 OK\" ) ; writer . WriteLine ( \"Content-Type: text/html\" ) ; writer . WriteLine ( ) ; // Now the body writer . Write ( \"<html><head><title>Hello World</title></head><body><h1>Hello FastCgiNet !</h1>The requested path was {0}</body></html>\" , requestedPath ) ; } // Our application status and end of request. The FastCgi Standard defines that returning 0 indicates there were no errors request . SendEndRequest ( 0 , ProtocolStatus . RequestComplete ) ; } // The connection socket and all other resources (except for the // listen socket) are automatically disposed at this point. This // implies that ApplicationSocketRequest still doesn\'t multiplex // requests (not for long, hopefully - also, you can inherit from // this class and make Dispose() not call CloseSocket() if // you want to multiplex requests). }
再一次,不要忘记处理各种插座和邪恶的Web服务器错误。
大量要求和内存消耗(应用程序侧)
如果您使用的是请求API,则可以轻松地将大型请求存储在磁盘上而不是在内存中。实际上,如果您只是复制并粘贴应用程序代码示例, FastCgiNet将自动为您存储大于2KB的请求。可以通过RecordFactory类中的构造函数轻松地指定此尺寸限制,该构造器可以提供给您的FastCgiRequest S(请注意,这在v0.1中未实现;它仅在Master上可用,并且可以在V0.11中可用)。
大量要求和内存消耗(WebServer侧)
托多
记录API
这是一个较低级别的API,可让您构建记录并亲自发送记录。强烈建议您不使用此API,原因有几个:
- 记录最多保存着65535个字节的内容。这意味着您必须将数据分解为多个记录以通过它,或者事情可能会错误地错误。
- 此API中没有请求的概念。您必须自己处理。
- 大量要求 – 想象文件上传 – 是记忆的巨大浪费。请求API可以在指定的限制后自动将记录内容的存储存储到磁盘上;直接处理记录时,执行此操作并不容易。
- 请求API进行了几项理智检查,使其非常有帮助。
该API是公开的,因为与请求API结合使用,电源用户可能会充分利用它(尽管我自己很少看到用例)。因此,我不会在记录此API方面付出太多努力,因为我将记录请求API。该代码的记录和直观非常完美,因此,如果需要,只需探索API,就可以了。
版本控制
该库仍在略有变化。作者非常努力地保持较高的向后兼容性,尽管某些行为发生了变化,甚至可以预期更改API,直到V0.2。达到v0.2后,我们将输入严格的版本控制方案,该方案将在此处进行详细记录。
托多
- 多路复用请求
- 类型GetValues,GetValuesResult,Abortrequest,Data和Unknowntype的记录
- 更加关注角色过滤器和授权者
