FastCgiNet

其他类别 2025-08-20

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服务器的观点:

  1. 当浏览器请求页面/URL时,Web服务器必须从应用程序请求答案。
  2. 然后,应用程序将编写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 = "rn"; writer.Write("Status: 200 OK"); writer.WriteLine("Content-Type: text/html"); writer.WriteLine(); // Now the body writer.Write("Hello World

Hello FastCgiNet !

The requested path was {0}", 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 ( "Hello World

Hello FastCgiNet !

The requested path was {0}"
, 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的记录
  • 更加关注角色过滤器和授权者
下载源码

通过命令行克隆项目:

git clone https://github.com/mzabani/FastCgiNet.git