Вы находитесь на странице: 1из 16

Gibraltar

HTTP Extension Specification (BGI)

Note: Generally, this project is not being discussed under NDA with any accounts or third parties.
Should you require permission to discuss this with a particular organization outside the company,
please contact the author.

Revision: 0.3
Date: May 8, 1995
Author(s): John Ludeman
Document: httpext.doc

Microsoft Confidential
Microsoft Confidential
1 Background
This specification describes the basics of writing an HTTP Server extension for the Microsoft Gibraltar
HTTP server. Traditionally, HTTP extensions have been referred to as “scripts”, as they have been
implemented under Unix text scripts processed by a processing engine like Perl or csh. Microsoft has
defined the Binary Gateway Interface (BGI) as a means to build efficient extensions to the Microsoft
HTTP server which exposes similar functionality as the Common Gateway Interface (CGI) used under
Unix. Unlike a CGI scripts, an HTTP Extension is implemented as a shared library (DLL), thus the
significant cost of spawning a CGI process per request is not incurred. This model also permits additional
efficiencies such as caching or state maintenance to be implemented by an extension. An HTTP extension
runs in the same process as the server and shares the pool of threads used by the server.

For an introduction to CGI programming, see http://hoohoo.ncsa.uiuc.edu/cgi/.

Anything that can be performed by a CGI script can be performed by an HTTP extension. Through
conditional compilation, it should be an easy task to have a single source module work as either an HTTP
extension or CGI script. The CGI interface is also supported under the Microsoft Gibraltar HTTP server,
but is only recommended for infrequent operations.

Microsoft Confidential
2 The DLL Entry Point
AN HTTP extension is contained in a DLL with an entry point name of HttpSEProc. Multiple extensions
can reside in the same DLL but they will need to branch based on the request. The prototype for the
entrypoint looks like:
int
HttpSEProc(
HTTP_EXT_CONTEXT * pec
);

The return code can be one of:

SE_STATUS_SUCCESS - The Extension has finished processing the request and the server
should disconnect
SE_STATUS_SUCCESS_AND_KEEP_CONN - The Extension has finished processing the
request and the server should wait for the next HTTP request if the client supports
persistent connections. The Extension should only return this if they were able to send
the correct Content-Length header to the client.
SE_STATUS_PENDING - The Extension has queued the request and will notify the server when
it has finished (see SE_REQ_DONE_WITH_SESSION under Server Services).
SE_STATUS_ERROR - The Extension encountered an error processing the request, the request
will not be logged. The Extension should send an explanation to the client why the
request failed.

Once an extension is loaded, it will remain loaded until the server is stopped. Thus, extensions can do
process attach and detach for any necessary initialization or cleanup. An extension is guaranteed there
will be no open client requests when the server shuts down.

Microsoft Confidential
3 Making a Request
When a client directly references the DLL or indirectly references it by using an associated file extension 1,
the server calls the DLL entry point impersonating the specified user. There are no differences in making
an extension request versus a CGI request except the URL reference is to a DLL rather then a EXE.
Below is a sample form that references the ODBC HTTP extension:
<html>
<title>ODBC Raid Database Query</title>
<body>
<h1>ODBC Raid Database Query</h1>
<hr>

<form METHOD="POST" ACTION="/scripts/httpodbc.dll/queries/bugs.qry">


E-Mail Name: <input text name="Assign" size=36><br>
<P>
<input type="submit" value="Do Query">
</body>
</html>

After the user types the information into the form, the browser generates a request to the server.

In this instance, a “POST” request will be sent to the HTTP extension “httpodbc.dll” that resides in the
“/scripts/” directory of the web data tree. The path info is the portion of the URL following the script
name, which is “/queries/bugs.qry”. The ODBC extension uses this information to locate the Web
Database Gateway (WDG) file to use for the query. The parameters the user typed will be sent as extra
data with the Content-Type of “application/x-www-form-urlencoded” 2.

Parameters for extension and CGI requests take the form of:

<field name>=<field value>&<field name>=<field value>&...

Spaces in field name or field values are replaced with a plus sign. Equal signs, ampersands or other
delimiting characters that are actually part of the request string are hex escaped, for example %4B. If no
value was specified for a field name, it will generally be sent with a blank field value, though this does
depend on the browser being used.

The extension is wrapped in a “try/except” clause so access violations or other exceptions will not directly
effect the server. The extension receives information about a request in the HTTP_EXT_CONTEXT
structure. It looks like:
typedef struct _HTTP_EXT_CONTEXT
{
//
// Structure revision level
//

UCHAR Revision;

//
// Private context information for the server
//

void * ServerContext;
unsigned long ulReserved;

//
// Server extension procedure for retrieving information,
// setting information or performing an action
1
Issue: need to document how extensions can build the association at setup.
2
If the form had specified a “GET” request, then the parameters the user typed would have been appended
onto the URL after a question mark. POST is preferred to GET for forms processing as some servers have
URL length limits.

Microsoft Confidential
//

PFN_SE_CALLBACK pfnSECallback;

//
// Provide common request info most scripts will need (same values
// as "METHOD", "QUERY_STR", "PATH_INFO" and "PATH_TRANSLATED")
//

char * pszMethod;
char * pszQuery;
char * pszPathInfo;
char * pszPathTrans;

//
// If the client specified a Content-length in their header,
// pvExtra points to a buffer of cbExtra bytes that the client
// sent after the header. pszContentType points to the client specified
// content type or NULL if none was specified.
//

char * pszContentType;
unsigned long cbExtra;
void * pvExtra;

} HTTP_EXT_CONTEXT, *PHTTP_EXT_CONTEXT;

Microsoft Confidential
4 Server Services for Extensions
The extension can call the pfnSECallback function to request information or have the HTTP server
perform some action, such as sending data. The prototype for the callback is:
typedef int (__cdecl *PFN_SE_CALLBACK)(
struct _HTTP_EXT_CONTEXT * pec,
enum SE_REQ_TYPE seReq,
void * pData,
unsigned long ul1,
unsigned long ul2
);

A return code of zero means the request successful, a non-zero return indicates an error occurred. The
types of requests that can be made are:
enum SE_REQ_TYPE
{
//
// Sends a 302 (URL Redirect) message to the client.
// No further processing is needed after this call.
//
// This operation is similar to specifying "URI: <URL>"
// in a CGI script header.
//
// pData - Pointer to zero terminated string of the URL
//

SE_REQ_SEND_URL_REDIRECT_RESP,

//
// Sends the data specified by the URL to the client
// as if the client had requested the URL.
//
// NOTE: The URL *must* be on the server and must
// not specify protocol information (i.e., must begin
// with a '/').
//
// No further processing is needed after this call.
//
// pData - Pointer to zero terminated string of the URL
//

SE_REQ_SEND_URL,

//
// Sends a complete HTTP server response header including
// the status, server version, message time and MIME version.
//
// Server extensions should append other information at the end,
// such as Content-type, Content-length etc followed by an extra
// '\r\n'.
//
// pData - Zero terminated string pointing to optional
// status string (i.e., "401 Access Denied") or NULL for
// the default response of "200 OK".
//
// ul1 - Zero terminated string pointing to optional data to be
// appended and set with the header. If NULL, the header will
// be terminated by \r\n pair.
//

SE_REQ_SEND_RESPONSE_HEADER,

//
// Get a CGI like environment variable from the server
//
// pData - Pointer to buffer to receive string
// ul1 - Size (in bytes) of buffer pointed at by pData

Microsoft Confidential
// ul2 - Pointer to CGI variable to retrieve
//
// Returns: -1 is returned if the buffer is too small to hold the string,
// 0 on success, non-zero for a system error
//

SE_REQ_GET_CGI_VAR,

//
// Sends a buffer to the client (i.e., socket send)
//
// pData - points to the buffer to send
// ul1 - is the number of bytes to send
//
// Returns the number of bytes sent, -1 on socket error.
//

SE_REQ_SEND_DATA,

//
// Receives data into the specified buffer (i.e., socket recv)
//
// pData - Points to buffer to receive data into
// ul1 - Number of bytes to receive
//
// Returns the number of bytes received, -1 on socket error.
//

SE_REQ_RECV_DATA,

//
// If the server extension wants to hold onto the session because
// they have extended processing requirements
// they need to tell the server when the session is free
// so the server can close it and free other related structures.
//
// ul1 - One of the SE_STATUS codes
//

SE_REQ_DONE_WITH_SESSION
};

Microsoft Confidential
5 Retrieving CGI Variables
When writing a CGI script, the various script parameters are passed in environment variables. When
writing an extension, the variable names remain the same except you retrieve them using the callback
function and a value of SE_REQ_GET_CGI_VAR. The following is a list of variables that can be
retrieved3:

The following environment variables are not request-specific and are set for all requests:

SERVER_SOFTWARE

The name and version of the information server software answering the request (and
running the gateway). Format: name/version

SERVER_NAME

The server's hostname, DNS alias, or IP address as it would appear in self-referencing


URLs.

GATEWAY_INTERFACE

The revision of the CGI specification to which this server complies. Format: CGI/revision

The following environment variables are specific to the request being fulfilled by the gateway
program:

SERVER_PROTOCOL

The name and revision of the information protcol this request came in with. Format:
protocol/revision

SERVER_PORT

The port number to which the request was sent.

REQUEST_METHOD

The method with which the request was made. For HTTP, this is "GET", "HEAD",
"POST", etc.

PATH_INFO

The extra path information, as given by the client. In other words, scripts can be accessed
by their virtual pathname, followed by extra information at the end of this path. The extra
information is sent as PATH_INFO. This information should be decoded by the server if it
comes from a URL before it is passed to the CGI script.

PATH_TRANSLATED

The server provides a translated version of PATH_INFO, which takes the path and does
any virtual-to-physical mapping to it.

SCRIPT_NAME

A virtual path to the script being executed, used for self-referencing URLs.

3
Retrieved from http://hoohoo.ncsa.uiuc.edu/cgi/env.htm.

Microsoft Confidential
QUERY_STRING

The information which follows the ? in the URL which referenced this script. This is the
query information. It should not be decoded in any fashion. This variable should always be
set when there is query information, regardless of command line decoding.

REMOTE_HOST

The hostname making the request. If the server does not have this information, it should set
REMOTE_ADDR and leave this unset.

REMOTE_ADDR

The IP address of the remote host making the request.

AUTH_TYPE

If the server supports user authentication, and the script is protects, this is the
protocol-specific authentication method used to validate the user.

REMOTE_USER

If the server supports user authentication, and the script is protected, this is the username
they have authenticated as.

REMOTE_IDENT

If the HTTP server supports RFC 931 identification, then this variable will be set to the
remote user name retrieved from the server. Usage of this variable should be limited to
logging only.

CONTENT_TYPE

For queries which have attached information, such as HTTP POST and PUT, this is the
content type of the data.

CONTENT_LENGTH

The length of the said content as given by the client.

In addition to these, the header lines received from the client, if any, are placed into the environment with
the prefix HTTP_ followed by the header name. Any - characters in the header name are changed to _
characters. The server may exclude any headers which it has already processed, such as Authorization,
Content-type, and Content-length. If necessary, the server may choose to exclude any or all of these
headers if including them would exceed any system environment limits.

An example of this is the HTTP_ACCEPT variable which was defined in CGI/1.0. Another example is the
header User-Agent.

HTTP_ACCEPT

The MIME types which the client will accept, as given by HTTP headers. Other protocols
may need to get this information from elsewhere. Each item in this list should be separated
by commas as per the HTTP spec.

Format: type/subtype, type/subtype

HTTP_USER_AGENT

Microsoft Confidential
The browser the client is using to send the request. General format: software/version
library/version.

In addition, a client can ask for ALL_HTTP which returns a complete list of headers passed by the client.
The headers are in the above format (i.e., upper cased with an “HTTP_” prefix), separated by a line feed
and zero terminated.

Microsoft Confidential
6 Sample HTTP Extension
The following extension is used for testing the extension interface and shows how to use the extension
capabilities.

Sample Code
/*++

Copyright (c) 1994 Microsoft Corporation

Module Name:

w3test.c

Abstract:

This module tests the web server's server extension interface

Author:

John Ludeman (johnl) 13-Oct-1994

Revision History:
--*/

#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#include <httpext.h>

DWORD WINAPI
SimulatePendIOThread(
LPDWORD lpParams
);

int WINAPI DoAction( HTTP_EXT_CONTEXT * pec,


char * pszAction );

HMODULE hmod;

int
HttpSEProc(
HTTP_EXT_CONTEXT * pec
)
{
if ( !strnicmp( pec->pszQuery,
"SimulatePendingIO",
17))
{
DWORD dwThreadId;

CloseHandle( CreateThread( NULL,


0,
(LPTHREAD_START_ROUTINE) SimulatePendIOThread,
pec,
0,
&dwThreadId ));

return SE_STATUS_PENDING;
}
else
{
return DoAction( pec,
pec->pszQuery );
}

Microsoft Confidential
int WINAPI DoAction( HTTP_EXT_CONTEXT * pec,
char * pszAction )
{
char buff[4096];
int ret;
int i;
int cb;

if ( !strcmp( pszAction,
"SE_REQ_SEND_URL_REDIRECT_RESP" ))
{
//
// pec->pszPathInfo is the URL to redirect to
//

ret = pec->pfnSECallback( pec,


SE_REQ_SEND_URL_REDIRECT_RESP,
pec->pszPathInfo,
0,
0 );
return !ret ? SE_STATUS_SUCCESS :
SE_STATUS_ERROR;
}
else if ( !strcmp( pszAction,
"SE_REQ_SEND_URL" ))
{
//
// pec->pszPathInfo is the URL to send
//

ret = pec->pfnSECallback( pec,


SE_REQ_SEND_URL,
pec->pszPathInfo,
0,
0 );

return !ret ? SE_STATUS_SUCCESS :


SE_STATUS_ERROR;
}
else if ( !strcmp( pszAction,
"SE_REQ_SEND_RESPONSE_HEADER" ))
{
wsprintf( buff,
"Content-type: text/html\r\n"
"\r\n"
"<head><title>Response header test</title></head>\n"
"<body><h1>HTTP status code supplied in the path info
was \"%s\"</h1></body>\n",
pec->pszPathInfo );

ret = pec->pfnSECallback( pec,


SE_REQ_SEND_RESPONSE_HEADER,
pec->pszPathInfo, // HTTP status code
(ULONG) buff,
0 );

if ( ret )
return SE_STATUS_ERROR;

return ret < 0 ? SE_STATUS_ERROR :


SE_STATUS_SUCCESS;
}
else if ( !strcmp( pszAction,
"SE_REQ_GET_CGI_VAR" ))
{
ret = pec->pfnSECallback( pec,
SE_REQ_SEND_RESPONSE_HEADER,
NULL,
(ULONG) "Content-Type: text/html\r\n"
"\r\n",
0 );

Microsoft Confidential
if ( ret )
return SE_STATUS_ERROR;

if ( ret = pec->pfnSECallback( pec,


SE_REQ_GET_CGI_VAR,
buff,
sizeof(buff),
(unsigned long)"ALL_HTTP" ))
{
return SE_STATUS_ERROR;
}

strcat( buff, "\r\n" );

cb = strlen( buff );

ret = pec->pfnSECallback( pec,


SE_REQ_SEND_DATA,
buff,
cb,
0 );

pec->cbSent += cb;

if ( ret < 0 )
return SE_STATUS_ERROR;

return SE_STATUS_SUCCESS;
}
else if ( !strcmp( pszAction,
"SimulateFault" ))
{
*((CHAR *)0xffffffff) = 'a';
return SE_STATUS_ERROR;
}

wsprintf( buff,
"Content-Type: text/html\r\n\r\n"
"<head><title>Unknown Test command</title></head>\n"
"<body><h1>Unknown Test Command</h1>\n"
"<p>Usage:"
"<p>Query string contains one of the following:"
"<p> SE_REQ_SEND_URL_REDIRECT_RESP, SE_REQ_SEND_URL,
SE_REQ_SEND_RESPONSE_HEADER, SE_REQ_GET_CGI_VAR, SimulateFault"
"<p> or SimulatePendingIO with one of the above action strings"
"<p>"
"<p> such as:"
"<p>"
"<p> http://computer/scripts/w3test.dll?SimulatePendingIO&SE_REQ_SEND_URL"
"<p>"
"<p> The Path info generally contains the URL or response to use"
"</body>\n");

pec->pfnSECallback( pec,
SE_REQ_SEND_RESPONSE_HEADER,
NULL,
(ULONG) buff,
0 );

return SE_STATUS_SUCCESS;
}

DWORD WINAPI
SimulatePendIOThread(
LPDWORD lpParams
)
{
HTTP_EXT_CONTEXT * pec = (HTTP_EXT_CONTEXT *) lpParams;
char * psz;

Sleep( 5000 );

Microsoft Confidential
psz = strchr( pec->pszQuery, '&' );

if ( psz )
psz++;
else
psz = "No action string specified";

DoAction( pec,
psz );

pec->pfnSECallback( pec,
SE_REQ_DONE_WITH_SESSION,
NULL,
0,
0 );

FreeLibraryAndExitThread( hmod,
0 );

return 0;
}

Microsoft Confidential
7 Revision History
Version Author Notes Date
0.1 JohnL Initial draft May 1, 1995
0.2 JohnL Update May 8, 1995
0.3 Jallard Cleanup, minor corrections June 13, 1995

Microsoft Confidential

Оценить