登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> 程序员学前班[不再更新,只读] >> 主题: ISAPI / php 日志     [回主站]     [分站链接]
标题
ISAPI / php 日志
我是马甲
浏览(0) + 2007-07-05 19:36:50 发表 编辑

关键字:

ISAPI 日志

关键词

HttpExtensionProc

EXTENSION_CONTROL_BLOCK

HttpExtensionProc 函数是主要的接口,传入的是 EXTENSION_CONTROL_BLOCK 结构. EXTENSION_CONTROL_BLOCK 还含有可供客户端调用的函数指针.例如下面的myserver代码.

ZeroMemory(&ExtCtrlBlk, sizeof(ExtCtrlBlk));
ExtCtrlBlk.cbSize = sizeof(ExtCtrlBlk);
ExtCtrlBlk.dwVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
ExtCtrlBlk.GetServerVariable = ISAPI_GetServerVariableExport;
ExtCtrlBlk.ReadClient = ISAPI_ReadClientExport;
ExtCtrlBlk.WriteClient = ISAPI_WriteClientExport;
ExtCtrlBlk.ServerSupportFunction = ISAPI_ServerSupportFunctionExport;
ExtCtrlBlk.ConnID = (HCONN) (connIndex + 1);
ExtCtrlBlk.dwHttpStatusCode = 200;
ExtCtrlBlk.lpszLogData[0] = '0';
ExtCtrlBlk.lpszMethod = (char*)td->request.cmd.c_str();
ExtCtrlBlk.lpszQueryString =(char*) td->request.uriOpts.c_str();
ExtCtrlBlk.lpszPathInfo = td->pathInfo.length() ? (char*)td->pathInfo.c_str() : (CHAR*)"" ;
if(td->pathInfo.length())
ExtCtrlBlk.lpszPathTranslated = (char*)td->pathTranslated.c_str();
else
ExtCtrlBlk.lpszPathTranslated = (char*)td->filenamePath.c_str();
ExtCtrlBlk.cbTotalBytes = td->inputData.getFileSize();
ExtCtrlBlk.cbAvailable = 0;
ExtCtrlBlk.lpbData = 0;
{
HttpRequestHeader::Entry *content =
td->request.other.get("Content-Type");
ExtCtrlBlk.lpszContentType = content ? (char*)content->value->c_str()
: 0;
}

connTable[connIndex].td->buffer->setLength(0);
connTable[connIndex].td->buffer->getAt(0)='\0';
HttpExtensionProc = (PFN_HTTPEXTENSIONPROC)appHnd.getProc("HttpExtensionProc");
if (HttpExtensionProc == NULL)
{
td->connection->host->warningsLogRequestAccess(td->id);
td->connection->host->warningsLogWrite(
"ISAPI: Failed to get pointer to HttpExtensionProc() \
in ISAPI application module");

td->connection->host->warningsLogTerminateAccess(td->id);
appHnd.close();
connTable[connIndex].chain.clearAllFilters();
return td->http->raiseHTTPError(500);
}

Ret = HttpExtensionProc(&ExtCtrlBlk);
if (Ret == HSE_STATUS_PENDING)
{
WaitForSingleObject(connTable[connIndex].ISAPIDoneEvent, timeout);
}


--------------------------------------------------
2007.7.7
isapi 下的 php.ini 会首先在服务器程序的当前目录中寻找. 文档中说可以通过 PHPRC 环境变量来设置,但在我这里似乎不行? php官方文档描述如下:

配置文件
配置文件(PHP 3 中是 php3.ini,自 PHP 4 起是 php.ini)在 PHP 启动时被读取。对于服务器模块版本的 PHP,仅在 web 服务器启动时读取一次。对于 CGI 和 CLI 版本,每次调用都会读取。

php.ini 的搜索路径如下(按顺序):


SAPI 模块所指定的位置(Apache 2 中的 PHPIniDir 指令,CGI 和 CLI 中的 -c 命令行选项,NSAPI 中的 php_ini 参数,THTTPD 中的 PHP_INI_PATH 环境变量)

HKEY_LOCAL_MACHINE\SOFTWARE\PHP\IniFilePath(Windows 注册表位置)

PHPRC 环境变量

当前工作目录(对于 CLI)

web 服务器目录(对于 SAPI 模块)或 PHP 所在目录(Windows 下其它情况)

Windows 目录(C:\windows 或 C:\winnt),或 --with-config-file-path 编译时选项指定的位置


如果存在 php-SAPI.ini(SAPI 是当前所用的 SAPI 名称,因此实际文件名为 php-cli.ini 或 php-apache.ini 等),则会用它替代 php.ini。SAPI 的名称可以用 php_sapi_name() 来测定。

注: Apache web 服务器在启动时会把目录转到根目录,这将导致 PHP 尝试在根目录下读取 php.ini,如果存在的话。

由扩展库处理的 php.ini 指令,其文档分别在各扩展库的页面。内核配置选项见附录。不过也许不是所有的 PHP 指令都在手册中有文档说明。要得到自己的 PHP 版本中的配置指令完整列表,请阅读 php.ini 文件,其中都有注释。此外,也许从 CVS 得到的最新版 php.ini 也有帮助。

--------------------------------------------------

我是马甲
2007-7-5 19:39:44 发表 编辑

函数指针中 ISAPI_ReadClientExport 用来读取客户端发送过来的信息(大于 48K 就要这样?). ISAPI_WriteClientExport 则可用来直接发送消息给客户端.
我是马甲
2007-7-5 19:44:12 发表 编辑

WEB服务CGI接口漏洞分析


分析了一段时间的CGI接口,感觉各种WEB服务器对一些变量好象不是很统一,也没明白一些安全要求,所以造成一些安全上的漏洞,在此作一简要分析。因为是根据个人的一些理解分析,所以错误在所难免,还望大家批评指正。

主要问题是几个变量PATH_INFO、PATH_TRANSLATED、SCRIPT_NAME的处理不统一。下面是MSDN的关于这几个变量的说明,为了清楚,也把相关两个一起列出,另两个变量这两种WEB服务器应该说都是正确处理。

PATH_INFO Additional path information, as given by the client. This comprises the trailing part of the URL after the script name but before the query string (if any).
PATH_TRANSLATED This is the value of PATH_INFO, but with any virtual path name expanded into a directory specification.
QUERY_STRING The information which follows the ? in the URL that referenced this script.
REQUEST_METHOD The HTTP request method.
SCRIPT_NAME The name of the script program being executed.

为了方便说清楚,也好让大家有个印象,举一个具体实例,看看这几个变量到底是指的什么吧。比如有映射.php,那么请求:

“GET /test.php/aaa/bbb?cgivartest HTTP/1.1”,REQUEST_METHOD=GET;QUERY_STRING=cgivartest;这是不容质疑的,那另三个变量是什么呢?个人觉得MSDN里面的定义还比较准确,就是上面那几个定义。照那么说来应该SCRIPT_NAME=test.php,这是那个要执行(也可能是被别的程序解释执行)的程序,PATH_INFO=/aaa/bbb,“script name”之后“query string”之前,假如WEB主目录是“d:\inetpub\wwwroot”,那么PATH_TRANSLATED=d:\inetpub\wwwroot\aaa\bbb。

但实际情况是如何的呢,让我们来看看。用常用的两种WEB服务器建立两个环境,以做对比,看看这两种WEB服务器的处理。
环境:
1、win2000+apache1.3.14+php4;
2、win2000+iis5.0+php4;
具体配置我就不说了,相信大家都是配置高手,不用我在此多费口舌。
在每个WEB服务里面建立一个文件test.php,文件内容如下:
phpinfo();
?>
大家一看就知道是干什么,简单建立这么一个文件,方便我们查看一些WEB变量。

1、apache下直接用php.exe加载;
http://192.168.8.48/php/php.exe/abcde.php/aa/..%5c../test.php?cgivartest

得到:
Environment
Variable Value

SCRIPT_FILENAME d:/php4/php.exe
GATEWAY_INTERFACE CGI/1.1
SERVER_PROTOCOL HTTP/1.1
REQUEST_METHOD GET
QUERY_STRING cgivartest
REQUEST_URI /php/php.exe/abcde.php/aa/..%5c../test.php?cgivartest
SCRIPT_NAME /php/php.exe/abcde.php/aa/..\..
PATH_INFO /abcde.php/aa/../../test.php
PATH_TRANSLATED d:\program files\apache group\apache\htdocs\test.php

2、apache下用映射加载:
http://192.168.8.48/AB.PHP/AA/..%5C../test.php?cgivartest

得到:
Environment
Variable Value

SCRIPT_FILENAME d:/php4/php.exe
GATEWAY_INTERFACE CGI/1.1
SERVER_PROTOCOL HTTP/1.1
REQUEST_METHOD GET
QUERY_STRING cgivartest
REQUEST_URI /AB.PHP/AA/..%5C../test.php?cgivartest
SCRIPT_NAME /php/php.exe/AB.PHP/AA/..\..
PATH_INFO /AB.PHP/AA/../../test.php
PATH_TRANSLATED d:\program files\apache group\apache\htdocs\test.php

3、iis下cgi接口加载:
http://192.168.8.48:81/abc.php/aa/..%c1%1c../test.php?cgivartest

得到:

Environment
Variable Value

GATEWAY_INTERFACE CGI/1.1
PATH_INFO /abc.php/aa/..\../test.php
PATH_TRANSLATED d:\inetpub\wwwroot\abc.php\aa\..\..\test.php
QUERY_STRING cgivartest
REQUEST_METHOD GET
SCRIPT_NAME /abc.php
SERVER_PROTOCOL HTTP/1.1

4、iis下isapi接口加载:(映射.php4)
http://192.168.8.48:81/abc.php4/aa/..%c1%1c../test.php?cgivartest

得到:

ISAPI
Server Variable Value
PATH_INFO /abc.php4/aa/..\../test.php
PATH_TRANSLATED d:\inetpub\wwwroot\abc.php4\aa\..\..\test.php
QUERY_STRING cgivartest
REQUEST_METHOD GET
SCRIPT_NAME /abc.php4
SERVER_PROTOCOL HTTP/1.1
URL /abc.php4

大家看看,apache、iis两者都不是执行的 SCRIPT_NAME,而是执行的PATH_TRANSLATED。个人理解PATH_INFO从其名字来看只是一个路径信息,而不是一个文件。而WEB服务本身应该说带有chroot性质,所以说变量PATH_INFO应该限制rfc的“/../”,这点iis是做到了,用%5c这种rfc里面的编码要求是不行的,%c1%1c的解码是因为另一个漏洞。而apache呢,直接用%5c就可以,这显然应该说是一个漏洞。假设要执行的是PATH_TRANSLATED,那么大家试试把test.php换成别的名,比如另一种映射的名test.pl,这4种情况同样照php执行了。那意思是什么呢,就是说我可以任意以一种解释程序去执行一种影射文件,那显然与这种映射所要求的安全不符合,也就很容易导致源代码泄露漏洞。如果限制了PATH_INFO里面的“/../”,那么是不能返回上级目录,这样就不能用任意解释程序去解释一种映射文件。但想想如果这样SCRIPT_NAME、PATH_INFO、PATH_TRANSLATED几个变量是不是就有变量根本就没什么意思呢?个人理解应该是执行的是SCRIPT_NAME程序,PATH_INFO变量只是再提供给SCRIPT_NAME的一个附加的路径信息,比如用于在这路径下读取一些配置文件等。你看apache的SCRIPT_NAME变量就去掉了后面的文件名。

我们总结得到:

1、PATH_INFO应该限制rfc编码要求内的“/../”。这点apache有漏洞。这点可能导致源代码泄露,甚至导致可以访问任意文件。
2、PATH_TRANSLATED、PATH_INFO、SCRIPT_NAME不清楚,执行文件不清楚。这点apache、iis两者都有漏洞。这点很容易导致源代码泄露。实际上发现的有很漏洞与这脱离不了关系。可能过一段时间就会有IIS的泄露源代码漏洞补丁了,同样方法在apache上面也可以,虽然此漏洞根本原因不是因为这,但这为那漏洞提供了表现机会。那攻击主要是利用了这个漏洞加上windows文件操作的文件名末尾半个汉字截断处理漏洞,可以查看一些asp、php等源代码,过一段时间大家就可以看到了。

有兴趣的可以照着这实验自己做试验,看看各种环境对这些变量的处理,理解理解,很可能你自己就很容易的找到一些新漏洞呢。



附1:MSDN关于CGI、ISAPI的接口文档资料。

EXTENSION_CONTROL_BLOCK Structure
The EXTENSION_CONTROL_BLOCK structure has the following form:

typedef struct _EXTENSION_CONTROL_BLOCK {

DWORD cbSize; //IN
DWORD dwVersion //IN
HCONN ConnID; //IN
DWORD dwHttpStatusCode; //OUT
CHAR lpszLogData[HSE_LOG_BUFFER_LEN]; //OUT
LPSTR lpszMethod; //IN
LPSTR lpszQueryString; //IN
LPSTR lpszPathInfo; //IN
LPSTR lpszPathTranslated; //IN
DWORD cbTotalBytes; //IN
DWORD cbAvailable; //IN
LPBYTE lpbData; //IN
LPSTR lpszContentType; //IN

BOOL ( WINAPI * GetServerVariable )
( HCONN hConn,
LPSTR lpszVariableName,
LPVOID lpvBuffer,
LPDWORD lpdwSize );

BOOL ( WINAPI * WriteClient )
( HCONN ConnID,
LPVOID Buffer,
LPDWORD lpdwBytes,
DWORD dwReserved );

BOOL ( WINAPI * ReadClient )
( HCONN ConnID,
LPVOID lpvBuffer,
LPDWORD lpdwSize );

BOOL ( WINAPI * ServerSupportFunction )
( HCONN hConn,
DWORD dwHSERRequest,
LPVOID lpvBuffer,
LPDWORD lpdwSize,
LPDWORD lpdwDataType );

} EXTENSION_CONTROL_BLOCK, *LPEXTENSION_CONTROL_BLOCK;

The server communicates with the ISA via the EXTENSION_CONTROL_BLOCK.

The references to IN and OUT above indicates whether the member applies to messages to the extension (IN) or from the extension (OUT).

Members

The EXTENSION_CONTROL_BLOCK structure contains the following fields:

cbSize

The size of this structure.

dwVersion

The version information of HTTP_FILTER_REVISION. The HIWORD has the major version number and the LOWORD has the minor version number.

ConnID

A unique number assigned by the HTTP server. It must not be modified.

dwHttpStatusCode

The status of the current transaction when the request is completed. Can be one of the following:

HTTP_STATUS_BAD_REQUEST

HTTP_STATUS_AUTH_REQUIRED

HTTP_STATUS_FORBIDDEN

HTTP_STATUS_NOT_FOUND

HTTP_STATUS_SERVER_ERROR

HTTP_STATUS_NOT_IMPLEMENTED
lpszLogData

Buffer of size HSE_LOG_BUFFER_LEN. Contains a null-terminated log information string, specific to the ISA, of the current transaction. This log information will be entered in the HTTP server log. Maintaining a single log file with both HTTP server and ISA transactions is very useful for administration purposes.

lpszMethod

The method with which the request was made. This is equivalent to the CGI variable REQUEST_METHOD.

lpszQueryString

Null-terminated string containing the query information. This is equivalent to the CGI variable QUERY_STRING.

lpszPathInfo

Null-terminated string containing extra path information given by the client. This is equivalent to the CGI variable PATH_INFO.

lpszPathTranslated

Null-terminated string containing the translated path. This is equivalent to the CGI variable PATH_TRANSLATED.

cbTotalBytes

The total number of bytes to be received from the client. This is equivalent to the CGI variable CONTENT_LENGTH. If this value is 0xffffffff, then there are four gigabytes or more of available data. In this case, CHttpServerContext::ReadClient should be called until no more data is returned.

cbAvailable

The available number of bytes (out of a total of cbTotalBytes) in the buffer pointed to by lpbData. If cbTotalBytes is the same as cbAvailable the variable lpbData will point to a buffer which contains all the data sent by the client. Otherwise cbTotalBytes will contain the total number of bytes of data received. The ISA will then need to use the callback function CHttpServerContext::ReadClient to read the rest of the data (starting from an offset of cbAvailable).

lpbData

Points to a buffer of size cbAvailable that has the data sent by the client.

lpszContentType

Null-terminated string containing the content type of the data sent by the client. This is equivalent to the CGI variable CONTENT_TYPE.

GetServerVariable

This function copies information (including CGI variables) relating to an HTTP connection, or to the server itself, into a buffer. GetServerVariable takes the following parameters:

hConn A handle to a connection.

lpszVariableName Null-terminated string indicating which variable is being requested. Variable names are:Variable Name Description
ALL_HTTP All HTTP headers that were not already parsed into one of the above variables. These variables are of the form HTTP_
.
AUTH_PASS This will retrieve the password corresponding to REMOTE_USER as supplied by the client. It will be a null-terminated string.
AUTH_TYPE Contains the type of authentication used. For example, if Basic authentication is used, the string will be "Basic". For Windows NT Challenge-response, it will be "NTLM". Other authentication schemes will have other strings. Because new authentication types can be added to Internet Server, it is not possible to list all possible strings. If the string is empty then no authentication is used.
CONTENT_LENGTH The number of bytes which the script can expect to receive from the client.
CONTENT_TYPE The content type of the information supplied in the body of a POST request.
GATEWAY_INTERFACE The revision of the CGI specification to which this server complies. The current version is CGI/1.1.
HTTP_ACCEPT Special case HTTP header. Values of the Accept: fields are concatenated, separated by ", ". For example, if the following lines are part of the HTTP header:
accept: */*; q=0.1
accept: text/html
accept: image/jpeg
then the HTTP_ACCEPT variable will have a value of:

*/*; q=0.1, text/html, image/jpeg

PATH_INFO Additional path information, as given by the client. This comprises the trailing part of the URL after the script name but before the query string (if any).
PATH_TRANSLATED This is the value of PATH_INFO, but with any virtual path name expanded into a directory specification.
QUERY_STRING The information which follows the ? in the URL that referenced this script.
REMOTE_ADDR The IP address of the client.
REMOTE_HOST The hostname of the client.
REMOTE_USER This contains the username supplied by the client and authenticated by the server.
REQUEST_METHOD The HTTP request method.
SCRIPT_NAME The name of the script program being executed.
SERVER_NAME The server's hostname (or IP address) as it should appear in self-referencing URLs.
SERVER_PORT The TCP/IP port on which the request was received.
SERVER_PROTOCOL The name and version of the information retrieval protocol relating to this request. Normally HTTP/1.0.
SERVER_SOFTWARE The name and version of the web server under which the CGI program is running.

lpvBuffer Pointer to buffer to receive the requested information.

lpdwSize Pointer to DWORD indicating the number of bytes available in the buffer. On successful completion the DWORD contains the number of bytes transferred into the buffer (including the null terminating byte).
WriteClient

Sends information to the client from the indicated buffer. WriteClient takes the following parameters:

ConnID A unique connection number assigned by the HTTP server.

Buffer Pointer to the buffer where the data is to be written.

lpdwBytes Pointer to the data to be written.

dwReserved Reserved for future use.
ReadClient

Reads information from the body of the Web client's HTTP request into the buffer supplied by the caller. ReadClient takes the following parameters:

ConnID A unique connection number assigned by the HTTP server.

lpvBuffer Pointer to the buffer area to receive the requested information.

lpdwSize Pointer to DWORD indicating the number of bytes available in the buffer. On return *lpdwSize will contain the number of bytes actually transferred into the buffer.
ServerSupportFunction

Provide the ISAs with some general-purpose functions as well as functions that are specific to HTTP server implementation. ServerSupportFunction takes the following parameters:

hConn A handle to a connection.

dwHSERRequest An HTTP Server Extension value. See CHttpServerContext::ServerSupportFunction for a list of possible values and related parameters.

lpvBuffer When used with HSE_REQ_SEND_RESPONSE_HEADER, it points to a null-terminated optional status string (i.e., "401 Access Denied"). If this buffer is null, a default response of "200 Ok" will be sent by this function. When used with HSE_REQ_DONE_WITH_SESSION, it points to a DWORD indicating the status code of the request.

lpdwSize When used with HSE_REQ_SEND_RESPONSE_HEADER, it points to the size of the buffer lpdwDataType.

lpdwDataType A null-terminated string pointing to optional headers or data to be appended and sent with the header. If NULL, the header will be terminated by a “\r\n” pair.
Comments

A server identifies files with the extensions .EXE and .BAT as CGI (Common Gateway Interface) executables. In addition, a server will identify a file with a DLL extension as a script to execute.

When the server loads the DLL, it calls the DLL at the entry point CHttpServer::GetExtensionVersion to get the version number of the HTTP_FILTER_REVISION the ISA is based on and a short text description for server administrators. For every client request, the CHttpServer::HttpExtensionProc entry point is called. The extension receives the commonly-needed information such as the query string, path info, method name, and the translated path.

See Also CHttpServerContext::ReadClient, CHttpServer::GetExtensionVersion, CHttpServer::HttpExtensionProc
我是马甲
2007-7-5 20:03:31 发表 编辑

一、ISAPI概述

  Microsoft的WEB服务器提供了不同的ISAPI,应用ISAPI能够开发出高性能的应用程序。
  ISAPI具有两类组件:ISAPI扩展和ISAPI过滤器,本文着重介绍ISAPI扩展的应用和开发。

  ISAPI应用程序通过DLL实现,DLL的特性使它能够作为WEB服务器自身的扩充来装载。在 WEB服务器的地址空间运行, 而且只在第一次请求时装载一次,以后每一个后续请求通 过创建一个线程(仅用一个简单的函数调用) 来完成,这比CGI创建一个进程要节约大量 的时间和空间等资源。

  ISAPI扩展通常代替传统Web应用程序中CGI脚本的位置,由客户触发,为其特殊请求服务。

------清单1--------------



1.<html>
2. <img src="myGetGrp.dll">
3.</html>


  如清单1所示代码,服务器将调用myGetGrp.dll中提供的函数得到一GIF图像文件数据发 送给客户浏览器,在这里,myGetGrp.dll就是一个ISAPI扩展。

  如果服务器确定将执行一个ISAPI扩展, 他首先检查此扩展是否已经装入高速缓存,若 没有,则指定的DLL被装载;装入DLL后,服务器就调用DLL中的HttpExtensionProc()函 数对请求提供服务, 这里是ISAPI程序员放置具体功能操作的位置,服务器将所有必要 的信息通过一结构类型参数传递给这个函数,包括请求本身的内容和程序员将用到的回 调函数等,用回调函数,可以将数据传递给用户以及执行其他的操作。

  注意:必须牢记服务器是启动多线程来处理同时接收到的多个请求的,所以必须正 确处理线程间的同步,否则将会导致数据破坏甚至系统崩溃。

  二、用C++ Builder开发ISAPI扩展应用程序

  C++ Builder是Inprise公司继Delphi之后开发的又一个通用的客户/服务器结构的 开发工具。 它使用了C++语言,可以产生更快速和更高效的代码。目前已成为继Visual Basic、Delphi之后,在32位Windows环境下最具有吸引力的开发工具之一。

  启动C++ Builder后,用File→New菜单项打开New Items对话框,在New页面下选中 Web Server Application选项,单击按纽,弹出一New Web Server Application对 话框,选中ISAPI/NSAPI Dynamic将生成一ISAPI扩展应用程序框架,其主模块缺省名为 Project.cpp,其中主要实现了DLL的三个输出函数,说明如下:

  1.1 GetExtensionVersion()函数 这是一个非常简单的函数,它唯一的目的是指定ISAPI版本,并给出扩展的描述。当DLL第一次被装载时,由服务器调用。这发生在HttpExtensionProc()函数第一次调用前,在此函数中,你所需做的全部就是用常量HSE_VERSION_MAJOR和HSE_VERSION_ MINOR(在Isapi2.hpp中定义)设置扩展版本域。并且返回一true值。

  GetExtensionVersion()函数实现实例见清单2。


BOOL_export WINAPI
GetextensionVersion(Isapi2::THSE_VERSION_INFO &Ver) { /*设置扩展版本域*/ Ver.dwExtensionVersion=MAKELONG(HSE_VERSION_MAJOR,HSE_VERSION_MINOR);
Ver.lpszExtensionDesc="Example ISAPI extension";//设置扩展描述域 return
true; //返回true }


  在此函数中,程序员还可以加入初始化代码,如全局变量的初始化等。

  1.2 HttpExtensionProc()函数

  HttpExtensionProc() 函数是扩展的功能实现部分,每次产生对扩展的请求,服务 器就调用这个函数,同时传递一类型为TEXTENSION_CONTROL_BLOCK结构的参数(ECB) , 这个结构在Isapi2.hpp中定义:

  struct TEXTENSION_CONTROL_BLOCK

  { unsigned cbSize;//结构大小 unsigned dwVersion;//版本信息 unsigned ConnID; /*正在被服务的连接的ID;调用回调函数时必须作为一参数传递*/

  unsigned dwHttpStatusCode;/*在HttpExtensionProc () 函数返回前,在此放置HTTP状态码,参见HTTP1.0规范定义*/

  char lpszLogData[80];//接收一记录信息字符串 char *lpszMethod;//命名请求方式的字符串的指针 char *lpszQueryString;//含一个GET请求查询的字符串指针 char *lpszPathInfo;//请求的字符串的指针 char *lpszPathTranslated;//把请求的字符串指针翻译为服务器的物理路径 unsigned cbTotalBytes;//请求中字节的全部数目

  unsigned cbAvailable;//lpbData缓冲区长度 void *lpbData;//POST请求的数据缓冲区指针 char
*lpszContentType;//识别请求的MIME内容类型的指针 TGetServerVariableProc GetServerVariable;
/*检索服务器变量值的回调函数指 针*/ TWriteClientProc WriteClient;//写数据给用户的回调函数的指针 TReadClientProc

  ReadClient;//检索用户数据的回调函数指针 TServerSupportFunctionProc ServerSupportFunction;
/*支持其他操作的回调函数指针*/ }

  这种结构包含服务请求和回调函数指针所需得信息,你可以调用它来获取信息或执 行操作,下面对其中的回调函数作一说明:

  (1)GetServerVariable函数

  原型为:

  typedef BOOL _stdcall (*TgetServerVarableProc) (int hConn,*VariableName, void *Buff,int &Size);

  调用这个函数来获取服务器变量(如CONTEXT_TYPE)和同请求一起收到的头部。如通 过请求, 则得到HTTP_COOKIE来检索Cookies头部的内容。参数说明:hConn为传入参数 的连ECB接句柄ConnID;VariableName为要检索的变量的名字(如HTTP_COOKIE);Buffer为 接收变量的缓冲区指针;Size为缓冲区大小,若由于缓冲区空间不够而失败,该值被改 变为必要的缓冲区大小。

  (2)WriteClient函数

  原型为:

  typedef BOOL _stdcall (*TWriteClientProc) (int ConnID,void *Buffer,int & Bytes,int dwReserver)

  调用这个函数来发送响应内容给用户, 参数说明:ConnID为传入参数ECB中的连接 句柄ConnID; Buffer为包含写数据缓冲区的指针; Bytes为缓冲区数据的字节数; dwReserver保留。

  (3)ReadClient函数

  原型为:

  typedef BOOL _stdcall (*TReadClientProc) (int ConnID, void *Buffer,int & Size)

  调用这个函数读取用户的附加数据,通过检验ECB中cbAvailable和cbTotalBytes的 值来确定是否调用此函数, 若cbTotalBytes大于cbAvailable;就表明有更多的数据需 要调用该函数去读取。 参数说明:ConnID为传入参数ECB中的连接句柄ConnID;Buffer 为读入数据存放的缓冲区;Size在调用时,传入Buffer缓冲区的大小,返回时,等于实 际读取的字节数。

  (4)ServerSupportFunction函数

  原型为:

  typedef BOOL _stdcall (*TServerSupportFunctionProc) (int hConn, int HSERRequest,void buffer,int &Size,PDWORD DataType);

  这个函数实现其他一些操作,参数说明:hConn为传入参数ECB的连接句柄ConnID, HSERRequest为要实现操作的常量值。Size为Buffer缓冲区的大小,Buffer缓冲区指针; DataType为数据类型指针;其中Buffer和DataType的含义根据HSERRequest的值变化。

  下面说明这个函数的几个主要操作(也就是HSERRequest的可用值,在Isapi2.hpp中 定义),以及对应不同的操作,参数Buffer,Size,DataType的不同含义:

  ●HSE_REQ_SEND_URL_REDIRECT_RESP: 重定向客户浏览器到另一个网址上的URL。 Buffer:指向一重定向目标URL字符串;DataType被忽略。

  ●HSE_REQ_SEND_URL: 重定向到本服务器上的一个URL,Buffer: 指向一重定向目标 URL字符串;DataType被忽略。

  ●HSE_REQ_SEND_RESPONSE_HEADER:发送响应头部给用户;Buffer: 指向包含头部的 字符串;DataType被忽略。

  ●HSE_REQ_DONE_WITH_SESSION: 通知服务器, 异步的请求处理已经完成。 Size, Buffer,DataType均被忽略。

  ●HSE_REQ_MAP_URL_TO_PATH: 映射一个逻辑路径到一个物理路径。Buffer:映射在 此缓冲区上完成;DataType被忽略。

  HttpExtensionProc()函数的返回值必须使以下四个值(在Isapi2.hpp中定义)中的一个:

  ●HSE_STATUS_SUCCESS:所有进程已完成。

  ●HSE_STATUS_SUCCESS_AND_KEEP_CONN:所有进程已经完成,但希望保持连接以继 续进一步的交互。

  ●HSE_STATUS_PENDING: 进程未完成。 当扩展异步完成进程时, 将以参数 HSERRequest=HSE_REQ_DONE_WITH_SESSION调用ServerSupportFunction(),以提醒服务 器进程已完成。

  ●HSE_STATUS_ERROR:进程由于错误已异常终止。

  清单3包含了

int _export WINAPI
HttpExtensionProc(Isapi2::TEXTENSION_CONTROL_BLOCK &ECB) { char my_string[256];
int length; strcpy(my_string,"200 OK/r/nContext-Type:text/html");
length=strlen(my_string); ECB.dwHttpStatusCode=200; ECB.ServerSupportFunction(ECB.ConnID,
HSE_REQ_SEND_RESPONSE_HEADER, my_ string,length,NULL);//发送头部 strcpy(my_string,"Hello
World!"); length=strlen(my_string); ECB.WriteClient(ECB.ConnID,my_string,length,0);
//发送数据给客户浏览器 return(HSE_STATUS_SUCCESS); }

  处理一个"Hello World"网页请求的简单 但必要的逻辑。

  这是一个简单的例子,实际应用的ISAPI扩展将需要做比这更多的工作。

  与GetExtenVersion()函数和TerninateExtension()函数不同,HttpExtensionProc ()函数对用户的行为产生作用。

  1.3 TerminateExtension()函数

  TerminateExtension() 函数在用户将卸载DLL时被调用,它是可选择的。传入参数 为dwFlages,类型为int,是以下两个值(在Isapi2.hpp中定义)中的一个:

  ●请求同意卸载DLL的HSE_TERM_ADVISORY_UNLOAD值。 函数返回true将允许服务器 卸载该DLL。

  ●强迫DLL清除并准备被卸载的HSE_TERM_MUST_UNLOAD值。 TerminateExtension()函数对服务器行为产生作用。编写完ISAPI扩展应用程序后,用C++ Builder的Project->菜单项功能,为ISAPI扩 展生成一个DLL,这个DLL就可以直接被作为ISAPI扩展使用。

  2.结束语

  本文介绍的是一种在C++ Builder开发环境下较为复杂的ISAPI扩展的实现方法,这 种方法对理解ISAPI扩展的工作方式有很大帮助,除此以外,在C++ Builder中有更简单 的方法来实现,即通过使用WebModule, 以及TISAPIApplication、TISAPIRequest、 TISAPIResponse等类。详细方法参见C++ Builder文档。

我是马甲
2007-7-5 20:06:45 发表 编辑

GetServerVariable 似乎是用来取附带的 HTTP 头信息的,例如 cookie .
我是马甲
2007-7-5 20:17:09 发表 编辑

php 中有这样的代码

static char *sapi_isapi_read_cookies(TSRMLS_D)
{
LPEXTENSION_CONTROL_BLOCK lpECB = (LPEXTENSION_CONTROL_BLOCK) SG(server_context);
char variable_buf[ISAPI_SERVER_VAR_BUF_SIZE];
DWORD variable_len = ISAPI_SERVER_VAR_BUF_SIZE;

if (lpECB->GetServerVariable(lpECB->ConnID, "HTTP_COOKIE", variable_buf, &variable_len)) {
return estrndup(variable_buf, variable_len);
} else if (GetLastError()==ERROR_INSUFFICIENT_BUFFER) {
char *tmp_variable_buf = (char *) emalloc(variable_len+1);

if (lpECB->GetServerVariable(lpECB->ConnID, "HTTP_COOKIE", tmp_variable_buf, &variable_len)) {
tmp_variable_buf[variable_len] = 0;
return tmp_variable_buf;
} else {
efree(tmp_variable_buf);
}
}
return estrndup("", sizeof("")-1);
}

--------------------------------------------------
但在 mysever 中还实现了两个专门的类型,似乎是取得全部,代码如下:


if (!strcmp(lpszVariableName, "ALL_HTTP"))
{

if(Isapi::buildAllHttpHeaders(ConnInfo->td,ConnInfo->connection, lpvBuffer, lpdwSize))
ret=1;

else
{
SetLastError(ERROR_INSUFFICIENT_BUFFER);
ret=0;
}

}else if(!strcmp(lpszVariableName, "ALL_RAW"))


我是马甲
2007-7-5 20:20:58 发表 编辑

另外这个函数中似乎不只是提供头信息才有的,例如php中有这样的代码

lpECB->GetServerVariable(lpECB->ConnID, "SCRIPT_NAME", static_variable_buf, &variable_len);

难道它从这里取脚本的名称?

我是马甲
2007-7-6 18:59:01 发表 编辑

用C++Builder开发ISAPI扩展应用程序 [打印本页] 登录 -> 注册 -> 回复主题 -> 发表主题


yuanyuan 2006-05-25 16:23




一、ISAPI概述 Microsoft的WEB服务器提供了不同的ISAPI,应用ISAPI能够开发出高性能的应用程序。

ISAPI具有两类组件:ISAPI扩展和ISAPI过滤器,本文着重介绍ISAPI扩展的应用和开发。

ISAPI应用程序通过DLL实现,DLL的特性使它能够作为WEB服务器自身的扩充来装载。在WEB服务器的地址空间运行, 而且只在第一次请求时装载一次,以后每一个后续请求通过创建一个线程(仅用一个简单的函数调用) 来完成,这比CGI创建一个进程要节约大量 的时间和空间等资源。

ISAPI扩展通常代替传统Web应用程序中CGI脚本的位置,由客户触发,为其特殊请求服务。

------清单1--------------

1.
2.
3.

如清单1所示代码,服务器将调用myGetGrp.dll中提供的函数得到一GIF图像文件数据发 送给客户浏览器,在这里,myGetGrp.dll就是一个ISAPI扩展。

如果服务器确定将执行一个ISAPI扩展, 他首先检查此扩展是否已经装入高速缓存,若没有,则指定的DLL被装载;装入DLL后,服务器就调用DLL中的HttpExtensionProc()函 数对请求提供服务, 这里是ISAPI程序员放置具体功能操作的位置,服务器将所有必要 的信息通过一结构类型参数传递给这个函数,包括请求本身的内容和程序员将用到的回 调函数等,用回调函数,可以将数据传递给用户以及执行其他的操作。

注意:必须牢记服务器是启动多线程来处理同时接收到的多个请求的,所以必须正确处理线程间的同步,否则将会导致数据破坏甚至系统崩溃。

二、用C++ Builder开发ISAPI扩展应用程序 C++ Builder是Inprise公司继Delphi之后开发的又一个通用的客户/服务器结构的 开发工具。 它使用了C++语言,可以产生更快速和更高效的代码。目前已成为继Visual Basic、Delphi之后,在32位Windows环境下最具有吸引力的开发工具之一。

启动C++ Builder后,用File→New菜单项打开New Items对话框,在New页面下选中 Web Server Application选项,单击按纽,弹出一New Web Server Application对 话框,选中ISAPI/NSAPI Dynamic将生成一ISAPI扩展应用程序框架,其主模块缺省名为 Project.cpp,其中主要实现了DLL的三个输出函数,说明如下:

1.1 GetExtensionVersion()函数 这是一个非常简单的函数,它唯一的目的是指定ISAPI版本,并给出扩展的描述。 当DLL第一次被装载时,由服务器调用。这发生在HttpExtensionProc()函数第一次调用 前, 在此函数中, 你所需做的全部就是用常量HSE_VERSION_MAJOR和HSE_VERSION_ MINOR(在Isapi2.hpp中定义)设置扩展版本域。并且返回一true值。

GetExtensionVersion()函数实现实例见清单2。

---------清单2-----------------

BOOL_export WINAPI GetextensionVersion(Isapi2::THSE_VERSION_INFO &Ver)
{ /*设置扩展版本域*/
Ver.dwExtensionVersion=MAKELONG(HSE_VERSION_MAJOR,HSE_VERSION_MINOR);
Ver.lpszExtensionDesc="Example ISAPI extension";//设置扩展描述域
return true; //返回true
}
在此函数中,程序员还可以加入初始化代码,如全局变量的初始化等。

1.2 HttpExtensionProc()函数

HttpExtensionProc() 函数是扩展的功能实现部分,每次产生对扩展的请求,服务器就调用这个函数,同时传递一类型为TEXTENSION_CONTROL_BLOCK结构的参数(ECB) , 这个结构在Isapi2.hpp中定义:

struct TEXTENSION_CONTROL_BLOCK
{
unsigned cbSize; //结构大小
unsigned dwVersion; //版本信息
unsigned ConnID; //正在被服务的连接的ID;调用回调函数时必须作为一参数传递
unsigned dwHttpStatusCode; //在HttpExtensionProc () 函数返回前,在此放置HTTP状态码,
//参见HTTP1.0规范定义
char lpszLogData[80]; //接收一记录信息字符串
char *lpszMethod; //命名请求方式的字符串的指针
char *lpszQueryString; //含一个GET请求查询的字符串指针
char *lpszPathInfo; //请求的字符串的指针
char *lpszPathTranslated; //把请求的字符串指针翻译为服务器的物理路径
unsigned cbTotalBytes; //请求中字节的全部数目
unsigned cbAvailable; //lpbData缓冲区长度
void *lpbData; //POST请求的数据缓冲区指针
char *lpszContentType; //识别请求的MIME内容类型的指针
TGetServerVariableProc GetServerVariable; /*检索服务器变量值的回调函数指针*/
TWriteClientProc WriteClient; //写数据给用户的回调函数的指针
TReadClientProc ReadClient; //检索用户数据的回调函数指针
TServerSupportFunctionProc ServerSupportFunction; /*支持其他操作的回调函数指针*/
}

这种结构包含服务请求和回调函数指针所需得信息,你可以调用它来获取信息或执 行操作,下面对其中的回调函数作一说明:

(1)GetServerVariable函数

原型为:

typedef BOOL _stdcall (*TgetServerVarableProc) (int hConn,*VariableName, void *Buff,int &Size);

调用这个函数来获取服务器变量(如CONTEXT_TYPE)和同请求一起收到的头部。如通 过请求, 则得到HTTP_COOKIE来检索Cookies头部的内容。参数说明:hConn为传入参数 ECB的连接句柄ConnID;VariableName为要检索的变量的名字(如HTTP_COOKIE);Buffer为 接收变量的缓冲区指针;Size为缓冲区大小,若由于缓冲区空间不够而失败,该值被改 变为必要的缓冲区大小。

(2)WriteClient函数

原型为:

typedef BOOL _stdcall (*TWriteClientProc) (int ConnID,void *Buffer,int & Bytes,int dwReserver)

调用这个函数来发送响应内容给用户, 参数说明:ConnID为传入参数ECB中的连接 句柄ConnID; Buffer为包含写数据缓冲区的指针; Bytes为缓冲区数据的字节数; dwReserver保留。

(3)ReadClient函数

原型为:

typedef BOOL _stdcall (*TReadClientProc) (int ConnID, void *Buffer,int & Size)

调用这个函数读取用户的附加数据,通过检验ECB中cbAvailable和cbTotalBytes的 值来确定是否调用此函数, 若cbTotalBytes大于cbAvailable;就表明有更多的数据需 要调用该函数去读取。 参数说明:ConnID为传入参数ECB中的连接句柄ConnID;Buffer 为读入数据存放的缓冲区;Size在调用时,传入Buffer缓冲区的大小,返回时,等于实 际读取的字节数。

(4)ServerSupportFunction函数

原型为:

typedef BOOL _stdcall (*TServerSupportFunctionProc) (int hConn, int HSERRequest,void buffer,int &Size,PDWORD DataType);

这个函数实现其他一些操作,参数说明:hConn为传入参数ECB的连接句柄ConnID, HSERRequest为要实现操作的常量值。Size为Buffer缓冲区的大小,Buffer缓冲区指针; DataType为数据类型指针;其中Buffer和DataType的含义根据HSERRequest的值变化。

下面说明这个函数的几个主要操作(也就是HSERRequest的可用值,在Isapi2.hpp中 定义),以及对应不同的操作,参数Buffer,Size,DataType的不同含义:

●HSE_REQ_SEND_URL_REDIRECT_RESP: 重定向客户浏览器到另一个网址上的URL。 Buffer:指向一重定向目标URL字符串;DataType被忽略。

●HSE_REQ_SEND_URL: 重定向到本服务器上的一个URL,Buffer: 指向一重定向目标 URL字符串;DataType被忽略。

●HSE_REQ_SEND_RESPONSE_HEADER:发送响应头部给用户;Buffer: 指向包含头部的 字符串;DataType被忽略。

●HSE_REQ_DONE_WITH_SESSION: 通知服务器, 异步的请求处理已经完成。 Size, Buffer,DataType均被忽略。

●HSE_REQ_MAP_URL_TO_PATH: 映射一个逻辑路径到一个物理路径。Buffer:映射在 此缓冲区上完成;DataType被忽略。

HttpExtensionProc()函数的返回值必须使以下四个值(在Isapi2.hpp中定义)中的一个:

●HSE_STATUS_SUCCESS:所有进程已完成。

●HSE_STATUS_SUCCESS_AND_KEEP_CONN:所有进程已经完成,但希望保持连接以继 续进一步的交互。

●HSE_STATUS_PENDING: 进程未完成。 当扩展异步完成进程时, 将以参数 HSERRequest=HSE_REQ_DONE_WITH_SESSION调用ServerSupportFunction(),以提醒服务 器进程已完成。

●HSE_STATUS_ERROR:进程由于错误已异常终止。

清单3包含了处理一个"Hello World"网页请求的简单 但必要的逻辑。

---------清单3------------------------

int _export WINAPI HttpExtensionProc(Isapi2::TEXTENSION_CONTROL_BLOCK &ECB)
{
char my_string[256];
int length;
strcpy(my_string,"200 OK/r/nContext-Type:text/html");
length=strlen(my_string);
ECB.dwHttpStatusCode=200;
ECB.ServerSupportFunction(ECB.ConnID, HSE_REQ_SEND_RESPONSE_HEADER, my_string,length,NULL);//发送头部 strcpy(my_string,"Hello World!");
length=strlen(my_string);
ECB.WriteClient(ECB.ConnID,my_string,length,0); //发送数据给客户浏览器
return(HSE_STATUS_SUCCESS);
}

这是一个简单的例子,实际应用的ISAPI扩展将需要做比这更多的工作。

与GetExtenVersion()函数和TerninateExtension()函数不同,HttpExtensionProc ()函数对用户的行为产生作用。

1.3 TerminateExtension()函数

TerminateExtension() 函数在用户将卸载DLL时被调用,它是可选择的。传入参数 为dwFlages,类型为int,是以下两个值(在Isapi2.hpp中定义)中的一个:

●请求同意卸载DLL的HSE_TERM_ADVISORY_UNLOAD值。 函数返回true将允许服务器 卸载该DLL。

●强迫DLL清除并准备被卸载的HSE_TERM_MUST_UNLOAD值。 TerminateExtension()函数对服务器行为产生作用。

编写完ISAPI扩展应用程序后,用C++ Builder的Project->菜单项功能,为ISAPI扩 展生成一个DLL,这个DLL就可以直接被作为ISAPI扩展使用。

2.结束语

本文介绍的是一种在C++ Builder开发环境下较为复杂的ISAPI扩展的实现方法,这 种方法对理解ISAPI扩展的工作方式有很大帮助,除此以外,在C++ Builder中有更简单 的方法来实现, 即通过使用WebModule, 以及TISAPIApplication、 TISAPIRequest、 TISAPIResponse等类。详细方法参见C++ Builder文档。

clq
2007-7-7 17:08:41 发表 编辑

HttpExtensionProc: 这是DLL的入口,就象是Delphi应用程序中的 begin...end 块

TerminateExtension: 这是个可选的程序,它可以用作清除其他内存分配的线程。


当您在创建ISAPI DLL的时候,您必须引用上面列出的三个函数中的头两个函数,执行这

两个函数是所有ISAPI编程的关键。

这三个语句都包含了“字输出”,使用这项术语是因为ISAPI DLLs扩充了因特网信息服务

器。(记住,因特网信息服务器指的是微软服务器。如果您要把一台NT服务器作为体格网页服务器的话,那么,这正是您所需的工具。ISAPI DLLs随NT4.0分发,在安装操作系统是自动安装。)

ISAPI提供了一个制作服务器可遵循的标准。例如,它可以把网景公司的复杂的NSAPI接口

压缩至相关的简练而优美的ISAPI来对NSAPI接口进行操作。

下面是这两个重要函数的声明


function GetExtensionVersion(var Ver: THSE_VERSION_INFO): BOOL; stdcall;

function HttpExtensionProc(var ECB: TExtensionControlBlock): DWORD; stdcall;


您只要把GetExtensionVersion粘贴到您的DLLs救行了.当ISAPI向公众发布新版本时您只需要做轻微的改动。


function GetExtensionVersion(var Ver: THSE_VERSION_INFO):

BOOL; stdcall;

begin

Ver.dwExtensionVersion := $00010000; // 1.0 support

Ver.lpszExtensionDesc := 'Delphi 2.0 ISAPI DLL'; // Description

Result := True;

end;


The parameter passed to this function is declared in HTTPEXT.PAS as follows:

有关的参数在HTTPEXT.PAS中声明如下:


PHSE_VERSION_INFO = ^THSE_VERSION_INFO;

THSE_VERSION_INFO = packed record

dwExtensionVersion: DWORD;

lpszExtensionDesc: array[0..HseMaxExtDLLNameLen-1] of Char;

end;


常量HseMaxExtDllNameLen 在声明中的值为256。纪录中的这两个变量是“自声明”的, 前一个包含了ISAPI的版本号[注:即变量dwExtensionVersion (译者)],后一个则表示用户定义的一个用来描述DLLs的字符串。

在您引用GetExtensionVersion语句的同时,您必须在您的DLL程序的DPR文件部分增添输

出部分。在您写这段语句时您还应该写下:


exports

GetExtensionVersion


HttpExtensionProc;


这就是您在建立这两个重要ISAPI DLL的函数时所要做的。下一步,使用 HttpExtensionProc,稍微复杂一点,因此我将把它作为一个独立的部分。


与 HttpExtensionProc 一起工作


HttpExtensionProc语句是DLL的入口。它的作用就好比C语言中的 main() 语句,或者

Delphi 中的begin...end 部分


这里有一个简单的使用GetExtensionVersion语句的例子


function HttpExtensionProc(var ECB: TExtensionControlBlock):

DWORD; stdcall;

var

ResStr: string;

StrLen: Integer;

begin

ECB.lpszLogData := 'Delphi DLL Log';

ECB.dwHTTPStatusCode := 200;

ResStr := '' +

'
Test server results
' +


'Hello from ISAPI
' +

'';


ResStr := Format(

'HTTP/1.0 200 OK'#13#10+

'Content-Type: text/html'#13#10+

'Content-Length: %d'#13#10+

'Content:'#13#10#13#10'%s'

[Length(ResStr)

ResStr]);


StrLen := Length(ResStr);

ECB.WriteClient(ECB.ConnID

Pointer(ResStr)

StrLen

0);

Result := HSE_STATUS_SUCCESS;

end;


如果您在浏览其中向这个DLL发出请求,那么您会得到一页这样的回应:


Test Server Results

Hello from ISAPI


函数体内的大部分域提供基本信息的简单的HTML代码密切相关。您还需要填写TExtensionControlBlock中的一些域,如下所示。

注意到在这个纪录里有一个叫做WriteClient的函数指针,您可以引用这个函数把信息传

送回浏览器。当呼叫这个函数时,您使用到了下面提到的TExtensionControl块中的ConnID字段。当函数被呼叫时,ConnID为您自动填充。

在察看函数的代码之前,请让我为您演示所有用到的上文提及的HttpExtensionProc函数

的ISAPI DLL的完整程序


library Isapi1;

library Isapi1;


uses

Windows

SysUtils

HTTPExt;


function GetExtensionVersion( var Ver: THSE_VERSION_INFO ): BOOL; stdcall;

begin

Ver.dwExtensionVersion := $00010000; // We're expecting version 1.0 support

Ver.lpszExtensionDesc := 'Written in Delphi 2.0';

Result := True;

end;


function HttpExtensionProc( var ECB: TEXTENSION_CONTROL_BLOCK ): DWORD;

stdcall;

var

ResStr: string;

StrLen: Integer;

begin

ECB.lpszLogData := 'Delphi DLL Log';

ECB.dwHTTPStatusCode := 200;

ResStr := '

' +

'

Test server results

' +

'

Isapi says hello to DevRel


';

ResStr := Format(

'HTTP/1.0 200 OK'#13#10+

'Content-Type: text/html'#13#10+

'Content-Length: %d'#13#10+

'Content:'#13#10#13#10'%s'

[Length(ResStr)

ResStr]);

StrLen := Length(ResStr);

ECB.WriteClient(ECB.ConnID

Pointer(ResStr)

StrLen

0);

Result := HSE_STATUS_SUCCESS;

end;


exports

GetExtensionVersion


HttpExtensionProc;


begin

end.


为了运行这个DLL程序,您应该把它复制到您的NT服务器下的脚本目录中去。在我的NT4.0 机器中

它就像这样:


c:\winnt\system32\inetsrv\scripts\mystuff\isapi1.dll


在这个例子中,我已经创建了我的名为“mystuff”的目录

它只不过是用来存储我创建的

ISAPI DLLs。您的目录,当然和我的机器上的不完全一样,取决于您的“inetsrv”目录位置和其它因素。

为成功调用这个DLL,您应该在您的HTML页上增添这个超链接:


ISAPI One


当用户点击这个超链接时,ISAPI1 Dll会被呼叫,然后字符串“Hello from ISAPI”会显

示在用户的浏览器上。如果您并不是把 ISAPI.DLL放在 mystuff 目录下,那么您应该修改上面的HTML代码来使之与您的情况适应。注意,您的目录必须与目录 inetsrv 有关,不应,也不能包含您的整个DLL所在的目录。


下面是呼叫的完整的HTML脚本:







My Home Page




This is the home page for my home computer.



ISAPI One




注意,如果您多次把程序ISAPI1.DLL复制到 mystuff 目录下,在每一次复制之前您应该

关掉网络服务器的万维网端口。这是因为,在第一次复制这个DLL时,您可以不受限制,但在此之后,它就属于服务器了。因此,在您复制第一次拷贝的更新版本时,因当关掉万维网服务。您可以使用网络管理程序来关掉万维网服务。这个程序应该在微软网络管理程序组(Microsoft Internet Server group)下面,在安装网络信息服务时被安装到程序管理器(Explorer/Program Manager)下。


与 TExtensionControlBlock 一起工作

通过本文中的这一要点,您能够建立您的第一个ISAPI DLL,并且能在第二台机器上的网

页浏览器调用它。

在本文中接下来的ISAPI的其余部分将会更加深入。

这里是HttpExtensionProc参数中比较复杂的部分


PExtensionControlBlock = ^TExtensionControlBlock;

TExtensionControlBlock = packed record

cbSize: DWORD; // = sizeof(TExtensionControlBlock)

dwVersion: DWORD; // version info of this spec

ConnID: HCONN; // Context Do not modify!

dwHttpStatusCode: DWORD; // HTTP Status code

// null terminated log info specific to this Extension DLL

lpszLogData: array [0..HSE_LOG_BUFFER_LEN-1] of Char;

lpszMethod: PChar; // REQUEST_METHOD

lpszQueryString: PChar; // QUERY_STRING

lpszPathInfo: PChar; // PATH_INFO

lpszPathTranslated: PChar; // PATH_TRANSLATED

cbTotalBytes: DWORD; // Total bytes from client

cbAvailable: DWORD; // Available number of bytes

lpbData: Pointer; // pointer to cbAvailable bytes

lpszContentType: PChar; // Content type of client data


GetServerVariable: TGetServerVariableProc;

clq
2007-7-7 21:35:58 发表 编辑

WEB SERVER如何支持CGI/PHP/Perl ___ 程序的核心内容(1)
来源: 作者: 出处:巧巧读书 2006-09-27 进入讨论组
关 键 词:邮件服务器 word web服务器 unix php

  来源: zerodj 发布

正文:
自己写一个函数来支持CGI运行...
思路:
当WEB SERVER的请求是一个执行文件,
那么就执行这个文件(利用进程),可是如果要传入数据给CGI该如何办,那么CGI运行后的结果,在自己的程序中该如何得到呀?





那么就可以是管道,来达到数据的共享!
WEB页提交的数据在自己的程序中是可以得到的(SOCKET来得到)!就把数据传入给管道!
CGI程序需要的数据就从管道中去读!

当CGI程序运行结束后,就将结果输出给管道,自己的程序去读管道中的内容!
然后用SOCKET将结果发送给IE/NS就可以了!

下面只是一个函数,
CGIFileName:要执行的CGI程序名称
pPostValue:WEB页提交的值(POST方法提交的)
pPipeValue:环境变量的值(GET方法提交的值是放在环境变量QUERY_STRING中的)
isStdRead:CGI程序是否需要从管道中读取值

注意:POST方法提交的值,在环境变量中有一个变量CONTENT_LENGTH是记录POST提交值的长度!
每个环境变量是用''\0''字符来隔开的!(QUERY_STRING=name=66&password=77\0CONTENT_LENGTH=0\0\0)
当环境变量结尾应使用\0\0来结束!
千万不用API:SetEnvironmentVariable()来设置环境变量,一但将POST的值写入管道传给CGI程序,那用这个API函数设置的环境变量会丢失.



AnsiString CGIScriptRun(AnsiString CGIFileName,AnsiString pPostValue,char *pPipeValue,bool isStdRead){

HANDLE hProcess,hWrite,hRead, //进程句柄,管道写句柄,管道读句柄
SECURITY_ATTRIBUTES sa, //安全性结构
STARTUPINFO mysi, //子进程窗口属性结构
PROCESS_INFORMATION mypi, //子进程信息
bool bTest, //API是否调用成功

//填充安全性结构使句柄被继承
sa.nLength=sizeof(SECURITY_ATTRIBUTES),
sa.lpSecurityDescriptor=NULL,
sa.bInheritHandle=true,

bTest=CreatePipe(&hRead,&hWrite,&sa,0), //创建管道

if(!bTest){
return "",
}

//填充进程启动信息
memset(&mysi,0,sizeof(STARTUPINFO)),
mysi.cb=sizeof(STARTUPINFO),
mysi.dwFlags=STARTF_USESTDHANDLES,

if(!isStdRead)
//如果CGI(或PHP程序)不要从PIPE中取值(用了GET方法),则使用标准输入
mysi.hStdInput=GetStdHandle(STD_INPUT_HANDLE),
else
//则使用从管道中读
mysi.hStdInput=hRead,
mysi.hStdOutput=hWrite,//CGI程序运行结束后,会将结果送给标准输出(屏幕),在这里告诉CGI让它把
//结果送出个建立的管道中
mysi.hStdError=hWrite, //CGI程序出错,将出错信息改从管道中输出,(也就是向管道中写进信息)

//创建子进程
bTest=CreateProcess(NULL,
CGIFileName.c_str(),NULL,
NULL,true,//true为让这个进程继承上面设置的一些输入输出信息
DETACHED_PROCESS,
(LPVOID)pPipeValue,NULL,&mysi,&mypi),
//(LPVOID)pPipeValue ---传的环境变量

if(bTest)hProcess=mypi.hProcess,
else return"",

if(isStdRead){
if(bTest){
CloseHandle(mypi.hThread),
Dword dwWritten,
bool bReturn,

char *g=pPostValue.c_str(),
//将提交的值写进管道
bReturn=WriteFile(hWrite,g,strlen(g),&dwWritten,NULL),
if(!bReturn)return "",
}

if(!bTest)return "",
//等待CGI程序执行完毕
WaitForSingleObject(hProcess,WAIT_ABANDONED),
}

//一定要关闭管道的读句柄
CloseHandle(hWrite),

char readBuf[100],
Dword bytesRead=0,

AnsiString CGIout="",
//上面的CGI程序执行完毕后已经将结果写进管道了,现在只要读出来就可以了!
while(ReadFile(hRead,readBuf,100,&bytesRead,NULL)){
readBuf[bytesRead]=''\0'',
CGIout=CGIout+AnsiString(readBuf),
}

CloseHandle(hRead),
CloseHandle(hProcess),

//返回执行结果用SOCKET发送个用户!
return CGIout,

}

CGI程序是从标准输入中读数据,输出给标准输出!
这里用了管道,在自己的程序中,建立PIPE,让CGI改从PIPE中读数据或从PIPE中输出运行结果!
如果你的CGI程序中使用了文件操作!
如:打开文件,将文内容输出!
注意这里的文件路径请使用绝对路径,如果要用相对路径的话,它的参考路径为你这个应用程序所在路径,而不是CGI的路径

zerodj@163.com

clq
2007-9-26 10:34:15 发表 编辑

来自 http://www.dvbbs.net/tech/perl/2006041949839.asp

实战 FastCGI(转)三 作者: 来源:
阅读 998 人次 , 2006-4-19 18:05:00





   3. 撰写 FastCGI 应用程序

撰写全新的 FastCGI 应用程序,或是将旧有的 CGI 程序改写成 FastCGI 应用程序都非常的简单,只要使用 fcgi-devkit 所附的 fcgi_stdio 函式库即可。

基本上,fcgi_stdio 函式库已被设计成让开发人员撰写 FastCGI 应用程序就像写一般 CGI 程序一样,同时做到程序保有和 CGI 最大的兼容度,又能享受到 FastCGI 所带来的优点。使用 fcgi_stdio 函式库的另一项好处是,编译出来的执行档可同时以 CGI 以及 FastCGI 的方式执行。

3.1 程序架构
对 CGI 程序而言,其生命期就是从一个联机请求 (request) 开始到联机结束。而 FastCGI 程序就像是比较『长命』的 CGI 程序,其生命期横跨不同的联机请求,可从 Web 服务器激活开始到 Web 服务器停止。

由于 FastCGI 程序长命的特性,它和一般 CGI 程序主要的差异就在于把初始化 (initialization) 的部份和处理联机请求的部份区分开来,程序的架构如下所示:

Initialization Code
Start of response loop

body of response loop
End of response loop

Initialization Code 的部份只会在 FastCGI 程序激活时执行一次,程序初始化的部份像是内存的配置,建立和数据库的联机等都可以写在这里。

而 Start of response loop 到 End of response loop 之间的程序在每次发生联机请求时就会执行,这部份的程序才是真正处理每次联机请求要做的事情。例如接受使用者输入的参数,从数据库取出资料,执行运算动作,回复结果给使用者等等。

一个简单的 FastCGI 程序如下 (tiny-fcgi.c)

#include "fcgi_stdio.h"
#include
void main(void)
{
/* Initialization Code */
int count = 0;
/* Start of response loop */
while(FCGI_Accept() >= 0) {
/* body of response loop */
printf("Content-type: text/html\r\n"
"\r\n"
"FastCGI Hello! (C, fcgi_stdio library)"
"

FastCGI Hello! (C, fcgi_stdio library)

"
"Request number %d running on host %s "
"Process ID: %d\n",
++count, getenv("SERVER_NAME"), getpid());
}
/* End of response loop */
}

3.2 引入 fcgi_stdio.h 标头档
开始撰写一个新的 FastCGI 应用程序时,必须引入 fcgi_stdio.h 这个标头档:

#include ``fcgi_stdio.h''
如果要改写旧有的 CGI 程序成 FastCGI 程序,请把原本引入 stdio.h 的部份换掉:

#ifndef FASTCGI
#include
#else
#include ``fcgi_stdio.h''
#endif

上面的写法是利用 C 的前置编译器做处理,如果在编译时加入 -DFASTCGI 的参数则引入 fcgi_stdio.h ,反之则否。假设我们把 fcgi_stdio.h 标头文件放在 /usr/local/include/fastcgi 这个目录下的话,为了在编译时引入 fcgi_stdio.h 标头档,请加入 -I/usr/local/include/fastcgi 的参数。

$ cc -I/usr/local/include/fastcgi -c program.c

注意
如果你的程序使用到其它的函式库,请务必检查这些函式库是否有使用到 stdio.h 这个档,如果有的话必须一并换成 fcgi_stdio.h。举例来说,假如你的程序用到 GD 函式库4来产生 GIF 图形,请记得把 gd.h 中引入 stdio.h 的部份换成 fcgi_stdio.h,否则遇到 IO 的函式时会发生错误 (Core Dump)。

3.3 FastCGI 处理循环
在 FastCGI 程序中负责处理联机请求的程序,必须用一个 while 循环包起来,利用 fcgi_stdio 函式库中的 FCGI_Accept() 函式库来判断是否有新的联机请求发生。

FastCGI 程序被激活后,首先进行初始化的动作,如变量宣告、内存配置、或是与数据库建立联机等。执行到 FCGI_Accept() 时程序就会暂停,等到当某一个联机请求发生时,FCGI_Accept() 会传回大于零的值,程序即刻进入循环的主体,可能是执行 SQL 指令,运算以及产生 HTML 的输出。处理完后又回到 FCGI_Accept() 循环的开始,等待下一个联机请求。

由此可见, FastCGI 激活之后,省去原本 CGI 程序中 fork,内存配置,建立数据库联机等步骤,处理每个联机请求的时间几乎等于 FCGI_Accept() 这个循环中运算所需的时间。如果妥善将每次联机请求时一样的程序代码从 FCGI_Accept() 循环抽出来,只保留每次会产生不同结果的部份,则程序处理每次联机请求的时间可以更短,整个网站的效率也可以大幅的提升。

了解 FastCGI 程序的基本架构后,可以发现一般的 CGI 程序,只要加入 FCGI_Accept() 这个 while 循环后,大概就可以变成 FastCGI 的程序了。

3.4 炼结 libfcgi.a 函式库
FastCGI 程序要炼结到 libfcgi.a 函式库才可正确产生出执行档,假设 libfcgi.a 的位置在 /usr/local/lib 这个目录下,我们在编译 (炼结时) 要加入 -L/usr/local/lib -lfcgi 的参数。


$ cc -o program.fcg program.o -L/usr/local/lib -lfcgi

3.5 撰写 FastCGI 程序的注意事项
由于 FastCGI 程序被激活后就常驻在内存之中,对每个 FastCGI 的执行行程 (running process) 而言,每次联机请求共享的变量空间是相同的,因些选写 FastCGI 程序要特别留意一些注意事项。

1. 记住,对每个 FastCGI 程序而言,只有 FCGI_Accept() 循环内的程序才是真正处理每次联机请求的主体,假设在程序中要读取和 CGI 有关的环境变量,或是使用者透过窗体 (form) 所输入的资料,必须在 FCGI_Accept() 循环内才处理。
2. 对每次联机请求会改变的变量,别忘了在处理新的联机请求前把变量初始化 (除非是有特殊用途) ,以避免变量值在不同的联机请求之间互相影响。
3. 在 FCGI_Accept() 循环中配置 (allocate) 的内存别忘了释放 (free)。在一般的 CGI 程序中, Memory Leak 不算是太大的问题,因为每处理完一次联机请求,所有用到的内存随着 CGI 程序的结束也被释放。但是 FastCGI 程序会一直常驻在内存中,如果在 FCGI_Accept() 中有配置额外的内存,在循环结束前没有释放掉,则每处理一次联机请求就吃掉一些系统的内存,多跑几次下来,系统资源大概就被耗光了。Memory leak 的问题是 FastCGI 最常见的错误,要特别小心处理。
4. FastCGI 程序和其所要引用的子程序中,用到 stdio.h 函式的部份,记得要改成 fcgi_stdio.h 。否则当程序在遇到 IO 的动作时就会发生 Core Dump 的情况。
5. 在 FCGI_Accept() 循环中使用 FCGI_Finish() 函式以取代 exit() 函式。原本 CGI 程序中发生错误时,可能直接呼叫 exit() 函式结束 CGI 行程,FastCGI 程序也可以如此,因为 mod_fastcgi 模块在 FastCGI 程序发生错误而意外结束时,会自动再激活另一个 FastCGI 的行程。但比较好的作法是呼叫 fcgi_stdio.h 中的 FCGI_Finish() 函式,呼叫 FCGI_Finish() 函式会跳出目前程序正在运算中的循环,回到 FCGI_Accept() 等待下一个联机请求。如此可以省去一些网站服务器激活新的 FastCGI 行程的负担。   



总数:10 页次:1/1 首页 尾页  
总数:10 页次:1/1 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1