New task-based ns_http with better argument processing.
/*
* The contents of this file are subject to the AOLserver Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://aolserver.com/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is AOLserver Code and related documentation
* distributed by AOL.
*
* The Initial Developer of the Original Code is America Online,
* Inc. Portions created by AOL are Copyright (C) 1999 America Online,
* Inc. All Rights Reserved.
*
* Alternatively, the contents of this file may be used under the terms
* of the GNU General Public License (the "GPL"), in which case the
* provisions of GPL are applicable instead of those above. If you wish
* to allow use of your version of this file only under the terms of the
* GPL and not to allow others to use your version of this file under the
* License, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the GPL.
* If you do not delete the provisions above, a recipient may use your
* version of this file under either the License or the GPL.
*/
/*
* tclhttp.c --
*
* Support for the ns_http command.
*/
static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/tclnhttp.c,v 1.1 2004/10/06 18:50:16 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__;
#include "nsd.h"
extern Tcl_ObjCmdProc NsTclHttpObjCmd;
typedef struct {
Ns_Task *task;
SOCKET sock;
char *error;
char *next;
size_t len;
int status;
Ns_Time timeout;
Ns_Time stime;
Ns_Time etime;
Tcl_DString ds;
} Http;
/*
* Local functions defined in this file
*/
static int HttpWaitCmd(NsInterp *itPtr, int objc, Tcl_Obj **objv);
static int HttpQueueCmd(NsInterp *itPtr, int objc, Tcl_Obj **objv, int run);
static int SetVar(Tcl_Interp *interp, Tcl_Obj *varPtr, Tcl_Obj *valPtr);
static int HttpConnect(Tcl_Interp *interp, char *method, char *url,
Ns_Set *hdrs, Tcl_Obj *bodyPtr, Http **httpPtrPtr);
static char *HttpResult(char *response, int *statusPtr, Ns_Set *hdrs);
static void HttpClose(Http *httpPtr);
static void HttpCancel(Http *httpPtr);
static void HttpAbort(Http *httpPtr);
static int GetHttp(NsInterp *itPtr, Tcl_Obj *obj, Http **httpPtrPtr);
static Ns_TaskProc HttpProc;
/*
* Static variables defined in this file.
*/
static Ns_TaskQueue *queue;
/*
*----------------------------------------------------------------------
*
* NsTclNHttpObjCmd --
*
* Implements the new ns_http to handle HTTP requests.
*
* Results:
* Standard Tcl result.
*
* Side effects:
* May queue an HTTP request.
*
*----------------------------------------------------------------------
*/
int
NsTclNHttpObjCmd(ClientData arg, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
NsInterp *itPtr = arg;
Http *httpPtr;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
int run = 0;
static CONST char *opts[] = {
"cancel", "cleanup", "run", "queue", "wait", NULL
};
enum {
HCancelIdx, HCleanupIdx, HRunIdx, HQueueIdx, HWaitIdx
} opt;
/*
* Check for redirect to old ns_http.
*/
if (itPtr->servPtr->tcl.oldhttp) {
return NsTclHttpObjCmd(arg, interp, objc, objv);
}
if (objc < 2) {
Tcl_WrongNumArgs(interp, 1, objv, "option ?args ...?");
return TCL_ERROR;
}
if (Tcl_GetIndexFromObj(interp, objv[1], opts, "option", 0,
(int *) &opt) != TCL_OK) {
return TCL_ERROR;
}
switch (opt) {
case HRunIdx:
run = 1;
/* FALLTHROUGH */
case HQueueIdx:
return HttpQueueCmd(itPtr, objc, objv, run);
break;
case HWaitIdx:
return HttpWaitCmd(itPtr, objc, objv);
break;
case HCancelIdx:
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "id");
return TCL_ERROR;
}
if (!GetHttp(itPtr, objv[2], &httpPtr)) {
return TCL_ERROR;
}
HttpAbort(httpPtr);
break;
case HCleanupIdx:
hPtr = Tcl_FirstHashEntry(&itPtr->https, &search);
while (hPtr != NULL) {
httpPtr = Tcl_GetHashValue(hPtr);
HttpAbort(httpPtr);
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&itPtr->https);
Tcl_InitHashTable(&itPtr->https, TCL_STRING_KEYS);
break;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* HttpQueueCmd --
*
* Implements "ns_http queue" subcommand.
*
* Results:
* Standard Tcl result.
*
* Side effects:
* May queue an HTTP request.
*
*----------------------------------------------------------------------
*/
static int
HttpQueueCmd(NsInterp *itPtr, int objc, Tcl_Obj **objv, int run)
{
Tcl_Interp *interp = itPtr->interp;
int new, i;
Tcl_HashEntry *hPtr;
Http *httpPtr;
char buf[100], *arg, *url;
char *method = "GET";
Ns_Set *hdrs = NULL;
Tcl_Obj *bodyPtr = NULL;
Ns_Time incr;
static CONST char *opts[] = {
"-method", "-timeout", "-body", "-headers", NULL
};
enum {
QMethodIdx, QTimeoutIdx, QBodyIdx, QHeadersIdx
} opt;
incr.sec = 2;
incr.usec = 0;
for (i = 2; i < objc; ++i) {
arg = Tcl_GetString(objv[i]);
if (arg[0] != '-') {
break;
}
if (Tcl_GetIndexFromObj(interp, objv[i], opts, "option", 0,
(int *) &opt) != TCL_OK) {
return TCL_ERROR;
}
if ((i + 2) == objc) {
Tcl_AppendResult(interp, "no argument given to ", opts[opt], NULL);
return TCL_ERROR;
}
++i;
switch (opt) {
case QMethodIdx:
method = Tcl_GetString(objv[i]);
break;
case QTimeoutIdx:
if (Ns_TclGetTimeFromObj(interp, objv[i], &incr) != TCL_OK) {
return TCL_ERROR;
}
break;
case QBodyIdx:
bodyPtr = objv[i];
break;
case QHeadersIdx:
if (Ns_TclGetSet2(interp, Tcl_GetString(objv[i]),
&hdrs) != TCL_OK) {
return TCL_ERROR;
}
break;
}
}
if ((objc - i) != 1) {
Tcl_WrongNumArgs(interp, 2, objv, "?flags? url");
return TCL_ERROR;
}
url = Tcl_GetString(objv[i]);
if (!HttpConnect(interp, method, url, hdrs, bodyPtr, &httpPtr)) {
return TCL_ERROR;
}
Ns_GetTime(&httpPtr->stime);
httpPtr->timeout = httpPtr->stime;
Ns_IncrTime(&httpPtr->timeout, incr.sec, incr.usec);
httpPtr->task = Ns_TaskCreate(httpPtr->sock, HttpProc, httpPtr);
if (run) {
Ns_TaskRun(httpPtr->task);
} else {
if (queue == NULL) {
Ns_MasterLock();
if (queue == NULL) {
queue = Ns_CreateTaskQueue("tclhttp");
}
Ns_MasterUnlock();
}
if (Ns_TaskEnqueue(httpPtr->task, queue) != NS_OK) {
HttpClose(httpPtr);
Tcl_AppendResult(interp, "could not queue http task", NULL);
return TCL_ERROR;
}
}
i = itPtr->https.numEntries;
do {
sprintf(buf, "http%d", i++);
hPtr = Tcl_CreateHashEntry(&itPtr->https, buf, &new);
} while (!new);
Tcl_SetHashValue(hPtr, httpPtr);
Tcl_SetResult(interp, buf, TCL_VOLATILE);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* HttpWaitCmd --
*
* Implements "ns_http wait" subcommand.
*
* Results:
* Standard Tcl result.
*
* Side effects:
* May queue an HTTP request.
*
*----------------------------------------------------------------------
*/
static int
HttpWaitCmd(NsInterp *itPtr, int objc, Tcl_Obj **objv)
{
Tcl_Interp *interp = itPtr->interp;
Tcl_Obj *valPtr;
Tcl_Obj *elapsedPtr = NULL;
Tcl_Obj *resultPtr = NULL;
Tcl_Obj *statusPtr = NULL;
Ns_Set *hdrs = NULL;
Ns_Time diff;
char *arg, *content;
Http *httpPtr;
int result = TCL_ERROR;
int i, status;
static CONST char *opts[] = {
"-elapsed", "-result", "-headers", "-status", NULL
};
enum {
WElapsedIdx, WResultIdx, WHeadersIdx, WStatusIdx
} opt;
for (i = 2; i < objc; ++i) {
arg = Tcl_GetString(objv[i]);
if (arg[0] != '-') {
break;
}
if (Tcl_GetIndexFromObj(interp, objv[i], opts, "option", 0,
(int *) &opt) != TCL_OK) {
return TCL_ERROR;
}
if (++i == objc) {
Tcl_AppendResult(interp, "no argument given to ", opts[opt], NULL);
return TCL_ERROR;
}
switch (opt) {
case WElapsedIdx:
elapsedPtr = objv[i];
break;
case WResultIdx:
resultPtr = objv[i];
break;
case WStatusIdx:
statusPtr = objv[i];
break;
case WHeadersIdx:
if (Ns_TclGetSet2(interp, Tcl_GetString(objv[i]),
&hdrs) != TCL_OK) {
return TCL_ERROR;
}
break;
}
}
if ((objc - i) != 1) {
Tcl_WrongNumArgs(interp, 2, objv, "?flags? id");
return TCL_ERROR;
}
if (!GetHttp(itPtr, objv[i], &httpPtr)) {
return TCL_ERROR;
}
if (Ns_TaskWait(httpPtr->task, NULL) != NS_OK) {
HttpCancel(httpPtr);
Tcl_AppendResult(interp, "timeout waiting for task", NULL);
return TCL_ERROR;
}
result = TCL_ERROR;
if (elapsedPtr != NULL) {
Ns_DiffTime(&httpPtr->etime, &httpPtr->stime, &diff);
valPtr = Tcl_NewObj();
Ns_TclSetTimeObj(valPtr, &diff);
if (!SetVar(interp, elapsedPtr, valPtr)) {
goto err;
}
}
if (httpPtr->error) {
Tcl_AppendResult(interp, "http failed: ", httpPtr->error, NULL);
goto err;
}
content = HttpResult(httpPtr->ds.string, &status, hdrs);
if (statusPtr != NULL &&
!SetVar(interp, statusPtr, Tcl_NewIntObj(status))) {
goto err;
}
if (resultPtr == NULL) {
Tcl_SetResult(interp, content, TCL_VOLATILE);
} else {
if (!SetVar(interp, resultPtr, Tcl_NewStringObj(content, -1))) {
goto err;
}
Tcl_SetBooleanObj(Tcl_GetObjResult(interp), 1);
}
result = TCL_OK;
err:
HttpClose(httpPtr);
return result;
}
/*
*----------------------------------------------------------------------
*
* GetHttp --
*
* Locate and remove the Http struct for a given id.
*
* Results:
* 1 on success, 0 otherwise.
*
* Side effects:
* Will update given httpPtrPtr with pointer to Http struct.
*
*----------------------------------------------------------------------
*/
static int
GetHttp(NsInterp *itPtr, Tcl_Obj *obj, Http **httpPtrPtr)
{
Tcl_HashEntry *hPtr;
char *id;
id = Tcl_GetString(obj);
hPtr = Tcl_FindHashEntry(&itPtr->https, id);
if (hPtr == NULL) {
Tcl_AppendResult(itPtr->interp, "no such request: ", id, NULL);
return 0;
}
*httpPtrPtr = Tcl_GetHashValue(hPtr);
Tcl_DeleteHashEntry(hPtr);
return 1;
}
/*
*----------------------------------------------------------------------
*
* SetVar --
*
* Set a variable by name. Convience routine for for HttpWaitCmd.
*
* Results:
* 1 on success, 0 otherwise.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
SetVar(Tcl_Interp *interp, Tcl_Obj *varPtr, Tcl_Obj *valPtr)
{
Tcl_Obj *errPtr;
Tcl_IncrRefCount(valPtr);
errPtr = Tcl_ObjSetVar2(interp, varPtr, NULL, valPtr,
TCL_PARSE_PART1|TCL_LEAVE_ERR_MSG);
Tcl_DecrRefCount(valPtr);
return (errPtr ? 1 : 0);
}
/*
*----------------------------------------------------------------------
*
* HttpConnect --
*
* Open a connection to the given URL host and construct
* an Http structure to fetch the file.
*
* Results:
* 1 if successful, 0 otherwise.
*
* Side effects:
* Updates httpPtrPtr with newly allocated Http struct.
*
*----------------------------------------------------------------------
*/
int
HttpConnect(Tcl_Interp *interp, char *method, char *url, Ns_Set *hdrs,
Tcl_Obj *bodyPtr, Http **httpPtrPtr)
{
Http *httpPtr = NULL;
SOCKET sock;
char *body, *host, *file, *port;
int i, len;
if (strncmp(url, "http://", 7) != 0 || url[7] == '\0') {
Tcl_AppendResult(interp, "invalid url: ", url, NULL);
return 0;
}
host = url + 7;
file = strchr(host, '/');
if (file != NULL) {
*file = '\0';
}
port = strchr(host, ':');
if (port == NULL) {
i = 80;
} else {
*port = '\0';
i = atoi(port+1);
}
sock = Ns_SockAsyncConnect(host, i);
if (port != NULL) {
*port = ':';
}
if (sock != INVALID_SOCKET) {
httpPtr = ns_malloc(sizeof(Http));
httpPtr->sock = sock;
httpPtr->error = NULL;
Tcl_DStringInit(&httpPtr->ds);
if (file != NULL) {
*file = '/';
}
Ns_DStringAppend(&httpPtr->ds, method);
Ns_StrToUpper(Ns_DStringValue(&httpPtr->ds));
Ns_DStringVarAppend(&httpPtr->ds, " ", file ? file : "/",
" HTTP/1.0\r\n", NULL);
if (file != NULL) {
*file = '\0';
}
Ns_DStringVarAppend(&httpPtr->ds,
"User-Agent: ", Ns_InfoServerName(), "/",
Ns_InfoServerVersion(), "\r\n"
"Connection: close\r\n"
"Host: ", host, "\r\n", NULL);
if (file != NULL) {
*file = '/';
}
if (hdrs != NULL) {
for (i = 0; i < Ns_SetSize(hdrs); i++) {
Ns_DStringVarAppend(&httpPtr->ds,
Ns_SetKey(hdrs, i), ": ",
Ns_SetValue(hdrs, i), "\r\n", NULL);
}
}
body = NULL;
if (bodyPtr != NULL) {
body = Tcl_GetStringFromObj(bodyPtr, &len);
if (len == 0) {
body = NULL;
}
}
if (body != NULL) {
Ns_DStringPrintf(&httpPtr->ds, "Content-Length: %d\r\n", len);
}
Tcl_DStringAppend(&httpPtr->ds, "\r\n", 2);
if (body != NULL) {
Tcl_DStringAppend(&httpPtr->ds, body, len);
}
httpPtr->next = httpPtr->ds.string;
httpPtr->len = httpPtr->ds.length;
}
if (file != NULL) {
*file = '/';
}
if (httpPtr == NULL) {
Tcl_AppendResult(interp, "connect to \"", url, "\" failed: ",
ns_sockstrerror(ns_sockerrno), NULL);
return 0;
}
*httpPtrPtr = httpPtr;
return 1;
}
/*
*----------------------------------------------------------------------
*
* HttpResult --
*
* Parse an Http response for the result body and headers.
*
* Results:
* Pointer to body within Http buffer.
*
* Side effects:
* Will append parsed response headers to given hdrs if
* not NULL and set HTTP status code in given statusPtr.
*
*----------------------------------------------------------------------
*/
static char *
HttpResult(char *response, int *statusPtr, Ns_Set *hdrs)
{
int firsthdr, major, minor, len;
char *eoh, *body, *p, save;
body = response;
eoh = strstr(response, "\r\n\r\n");
if (eoh != NULL) {
body = eoh + 4;
eoh += 2;
} else {
eoh = strstr(response, "\n\n");
if (eoh != NULL) {
body = eoh + 2;
eoh += 1;
}
}
if (eoh == NULL) {
*statusPtr = 0;
} else {
*eoh = '\0';
sscanf(response, "HTTP/%d.%d %d", &major, &minor, statusPtr);
if (hdrs != NULL) {
save = *body;
*body = '\0';
firsthdr = 1;
p = response;
while ((eoh = strchr(p, '\n')) != NULL) {
*eoh++ = '\0';
len = strlen(p);
if (len > 0 && p[len-1] == '\r') {
p[len-1] = '\0';
}
if (firsthdr) {
if (hdrs->name != NULL) {
ns_free(hdrs->name);
}
hdrs->name = ns_strdup(p);
firsthdr = 0;
} else if (Ns_ParseHeader(hdrs, p, ToLower) != NS_OK) {
break;
}
p = eoh;
}
*body = save;
}
}
return body;
}
static void
HttpClose(Http *httpPtr)
{
Ns_TaskFree(httpPtr->task);
Tcl_DStringFree(&httpPtr->ds);
ns_sockclose(httpPtr->sock);
ns_free(httpPtr);
}
static void
HttpCancel(Http *httpPtr)
{
Ns_TaskCancel(httpPtr->task);
Ns_TaskWait(httpPtr->task, NULL);
}
static void
HttpAbort(Http *httpPtr)
{
HttpCancel(httpPtr);
HttpClose(httpPtr);
}
/*
*----------------------------------------------------------------------
*
* HttpProc --
*
* Task callback for ns_http connections.
*
* Results:
* None.
*
* Side effects:
* Will call Ns_TaskCallback and Ns_TaskDone to manage state
* of task.
*
*----------------------------------------------------------------------
*/
static void
HttpProc(Ns_Task *task, SOCKET sock, void *arg, int why)
{
Http *httpPtr = arg;
char buf[1024];
int n;
switch (why) {
case NS_SOCK_INIT:
Ns_TaskCallback(task, NS_SOCK_WRITE, &httpPtr->timeout);
return;
break;
case NS_SOCK_WRITE:
n = send(sock, httpPtr->next, httpPtr->len, 0);
if (n < 0) {
httpPtr->error = "send failed";
} else {
httpPtr->next += n;
httpPtr->len -= n;
if (httpPtr->len == 0) {
shutdown(sock, 1);
Tcl_DStringTrunc(&httpPtr->ds, 0);
Ns_TaskCallback(task, NS_SOCK_READ, &httpPtr->timeout);
}
return;
}
break;
case NS_SOCK_READ:
n = recv(sock, buf, sizeof(buf), 0);
if (n > 0) {
Tcl_DStringAppend(&httpPtr->ds, buf, n);
return;
}
if (n < 0) {
httpPtr->error = "recv failed";
}
break;
case NS_SOCK_TIMEOUT:
httpPtr->error = "timeout";
break;
case NS_SOCK_EXIT:
httpPtr->error = "shutdown";
break;
case NS_SOCK_CANCEL:
httpPtr->error = "cancelled";
break;
}
/*
* Get completion time and mark task as done.
*/
Ns_GetTime(&httpPtr->etime);
Ns_TaskDone(httpPtr->task);
}
|
Back to SourceForge.net Powered by ViewCVS 1.0-dev |