Exemple #1
0
MaRequest *maCreateRequest(MaConn *conn)
{
    MaRequest   *req;
    MprHeap     *arena;

    arena  = mprAllocHeap(conn->arena, "request", MA_REQ_MEM, 0, NULL);
    if (arena == 0) {
        return 0;
    }
    req = mprAllocObjWithDestructorZeroed(arena, MaRequest, destroyRequest);
    if (req == 0) {
        return 0;
    }
    req->conn = conn;
    req->arena = arena;
    req->length = -1;
    req->ifMatch = 1;
    req->ifModified = 1;
    req->host = conn->host;
    req->remainingContent = 0;
    req->method = 0;
    req->headers = mprCreateHash(req, MA_VAR_HASH_SIZE);
    req->formVars = mprCreateHash(req, MA_VAR_HASH_SIZE);
    req->httpProtocol = "HTTP/1.1";
    return req;
}
Exemple #2
0
void maSetHostTraceFilter(MaHost *host, int len, cchar *include, cchar *exclude)
{
    char    *word, *tok, *line;

    host->traceMaxLength = len;

    if (include && strcmp(include, "*") != 0) {
        host->traceInclude = mprCreateHash(host, 0);
        line = mprStrdup(host, include);
        word = mprStrTok(line, ", \t\r\n", &tok);
        while (word) {
            if (word[0] == '*' && word[1] == '.') {
                word += 2;
            }
            mprAddHash(host->traceInclude, word, host);
            word = mprStrTok(NULL, ", \t\r\n", &tok);
        }
        mprFree(line);
    }
    if (exclude) {
        host->traceExclude = mprCreateHash(host, 0);
        line = mprStrdup(host, exclude);
        word = mprStrTok(line, ", \t\r\n", &tok);
        while (word) {
            if (word[0] == '*' && word[1] == '.') {
                word += 2;
            }
            mprAddHash(host->traceExclude, word, host);
            word = mprStrTok(NULL, ", \t\r\n", &tok);
        }
        mprFree(line);
    }
}
Exemple #3
0
static void testCreateTable(MprTestGroup *gp)
{
    MprHash     *table;

    table = mprCreateHash(211, 0);
    assert(table != 0);

    table = mprCreateHash(0, 0);
    assert(table != 0);

    table = mprCreateHash(1, 0);
    assert(table != 0);
}
Exemple #4
0
EcLexer *ecCreateLexer(EcCompiler *cp)
{
    EcLexer         *lp;
    ReservedWord    *rp;
    int             size;

    lp = mprAllocObjWithDestructorZeroed(cp, EcLexer, destroyLexer);
    if (lp == 0) {
        return 0;
    }

    lp->input = mprAllocObjZeroed(lp, EcInput);
    if (lp->input == 0) {
        mprFree(lp);
        return 0;
    }
    lp->input->lexer = lp;
    lp->input->compiler = cp;
    lp->compiler = cp;

    size = sizeof(keywords) / sizeof(ReservedWord);
    lp->keywords = mprCreateHash(lp, size);
    if (lp->keywords == 0) {
        mprFree(lp);
        return 0;
    }

    for (rp = keywords; rp->name; rp++) {
        mprAddHash(lp->keywords, rp->name, rp);
    }

    return lp;
}
Exemple #5
0
/*
    This clones a database to give a private view per user.
 */
static int cloneDatabase(HttpConn *conn)
{
    Esp         *esp;
    EspRoute    *eroute;
    EspReq      *req;
    cchar       *id;

    req = conn->reqData;
    eroute = conn->rx->route->eroute;
    assert(eroute->edi);
    assert(eroute->edi->flags & EDI_PRIVATE);

    esp = req->esp;
    if (!esp->databases) {
        lock(esp);
        if (!esp->databases) {
            esp->databases = mprCreateHash(0, 0);
            esp->databasesTimer = mprCreateTimerEvent(NULL, "esp-databases", 60 * 1000, pruneDatabases, esp, 0);
        }
        unlock(esp);
    }
    /*
        If the user is logging in or out, this will create a redundant session here.
     */
    httpGetSession(conn, 1);
    id = httpGetSessionID(conn);
    if ((req->edi = mprLookupKey(esp->databases, id)) == 0) {
        if ((req->edi = ediClone(eroute->edi)) == 0) {
            mprLog("error esp", 0, "Cannot clone database: %s", eroute->edi->path);
            return MPR_ERR_CANT_OPEN;
        }
        mprAddKey(esp->databases, id, req->edi);
    }
    return 0;
}
Exemple #6
0
/*
    IndexName is not implemented yet
 */
static int mdbAddIndex(Edi *edi, cchar *tableName, cchar *columnName, cchar *indexName)
{
    Mdb         *mdb;
    MdbTable    *table;
    MdbCol      *col;

    mprAssert(edi);
    mprAssert(tableName && *tableName);
    mprAssert(columnName && *columnName);

    mdb = (Mdb*) edi;
    lock(mdb);
    if ((table = lookupTable(mdb, tableName)) == 0) {
        unlock(mdb);
        return MPR_ERR_CANT_FIND;
    }
    if ((col = lookupColumn(table, columnName)) == 0) {
        unlock(mdb);
        return MPR_ERR_CANT_FIND;
    }
    if ((table->index = mprCreateHash(0, MPR_HASH_STATIC_VALUES)) == 0) {
        unlock(mdb);
        return MPR_ERR_MEMORY;
    }
    table->indexCol = col;
    col->flags |= EDI_INDEX;
    autoSave(mdb, table);
    unlock(mdb);
    return 0;
}
Exemple #7
0
PUBLIC HttpTx *httpCreateTx(HttpConn *conn, MprHash *headers)
{
    HttpTx      *tx;

    if ((tx = mprAllocObj(HttpTx, manageTx)) == 0) {
        return 0;
    }
    conn->tx = tx;
    tx->conn = conn;
    tx->status = HTTP_CODE_OK;
    tx->length = -1;
    tx->entityLength = -1;
    tx->chunkSize = -1;

    tx->queue[HTTP_QUEUE_TX] = httpCreateQueueHead(conn, "TxHead");
    conn->writeq = tx->queue[HTTP_QUEUE_TX]->nextQ;
    tx->queue[HTTP_QUEUE_RX] = httpCreateQueueHead(conn, "RxHead");
    conn->readq = tx->queue[HTTP_QUEUE_RX]->prevQ;

    if (headers) {
        tx->headers = headers;
    } else {
        tx->headers = mprCreateHash(HTTP_SMALL_HASH_SIZE, MPR_HASH_CASELESS);
        if (!conn->endpoint) {
            httpAddHeaderString(conn, "User-Agent", sclone(BIT_HTTP_SOFTWARE));
        }
    }
    return tx;
}
Exemple #8
0
/*
 *  Dynamic module initialization
 */
MprModule *maEgiHandlerInit(MaHttp *http, cchar *path)
{
    MprModule   *module;
    MaStage     *handler;
    MaEgi       *egi;

    module = mprCreateModule(http, "egiHandler", BLD_VERSION, NULL, NULL, NULL);
    if (module == 0) {
        return 0;
    }

    handler = maCreateHandler(http, "egiHandler", 
        MA_STAGE_GET | MA_STAGE_HEAD | MA_STAGE_POST | MA_STAGE_PUT | MA_STAGE_FORM_VARS | MA_STAGE_ENV_VARS | \
        MA_STAGE_VIRTUAL);
    if (handler == 0) {
        mprFree(module);
        return 0;
    }
    http->egiHandler = handler;

    handler->run = runEgi; 

    handler->stageData = egi = mprAllocObjZeroed(handler, MaEgi);
    egi->forms = mprCreateHash(egi, MA_EGI_HASH_SIZE);

#if EGI_TEST
    egiTestInit(http, path);
#endif

    return module;
}
Exemple #9
0
int maAddUser(MaAuth *auth, cchar *realm, cchar *user, cchar *password, bool enabled)
{
    MaUser  *up;

    char    *key;

    up = maCreateUser(auth, realm, user, password, enabled);
    if (up == 0) {
        return MPR_ERR_NO_MEMORY;
    }

    if (auth->users == 0) {
        auth->users = mprCreateHash(auth, -1);
    }
    key = mprStrcat(auth, -1, realm, ":", user, NULL);
    if (mprLookupHash(auth->users, key)) {
        mprFree(key);
        return MPR_ERR_ALREADY_EXISTS;
    }

    if (mprAddHash(auth->users, key, up) == 0) {
        mprFree(key);
        return MPR_ERR_NO_MEMORY;
    }
    mprFree(key);
    return 0;
}
Exemple #10
0
static EjsService *createService()
{
    EjsService  *sp;

    if (MPR->ejsService) {
        return MPR->ejsService;
    }
    if ((sp = mprAllocObj(EjsService, manageEjsService)) == NULL) {
        return 0;
    }
    mprGlobalLock();
    MPR->ejsService = sp;
#if FUTURE && KEEP
    mprSetMemNotifier((MprMemNotifier) allocNotifier);
#endif
    sp->nativeModules = mprCreateHash(-1, MPR_HASH_STATIC_KEYS);
    sp->mutex = mprCreateLock();
    sp->vmlist = mprCreateList(-1, MPR_LIST_STATIC_VALUES);
    sp->vmpool = mprCreateList(-1, MPR_LIST_STATIC_VALUES);
    sp->intern = ejsCreateIntern(sp);
    sp->dtoaSpin[0] = mprCreateSpinLock();
    sp->dtoaSpin[1] = mprCreateSpinLock();
    ejsInitCompiler(sp);
    mprGlobalUnlock();
    return sp;
}
Exemple #11
0
bool mprRemoveCache(MprCache *cache, cchar *key)
{
    CacheItem   *item;
    bool        result;

    mprAssert(cache);
    mprAssert(key && *key);

    if (cache->shared) {
        cache = cache->shared;
        mprAssert(cache == shared);
    }
    lock(cache);
    if (key) {
        if ((item = mprLookupKey(cache->store, key)) != 0) {
            cache->usedMem -= (slen(key) + slen(item->data));
            mprRemoveKey(cache->store, key);
            result = 1;
        } else {
            result = 0;
        }

    } else {
        /* Remove all keys */
        result = mprGetHashLength(cache->store) ? 1 : 0;
        cache->store = mprCreateHash(CACHE_HASH_SIZE, 0);
        cache->usedMem = 0;
    }
    unlock(cache);
    return result;
}
Exemple #12
0
/*
    Parent may be null
 */
PUBLIC HttpTrace *httpCreateTrace(HttpTrace *parent)
{
    HttpTrace   *trace;

    if ((trace = mprAllocObj(HttpTrace, manageTrace)) == 0) {
        return 0;
    }
    if (parent) {
        *trace = *parent;
        trace->parent = parent;
    } else {
        if ((trace->events = mprCreateHash(0, MPR_HASH_STATIC_VALUES)) == 0) {
            return 0;
        }
        mprAddKey(trace->events, "request", ITOP(1));
        mprAddKey(trace->events, "result", ITOP(2));
        mprAddKey(trace->events, "error", ITOP(2));
        mprAddKey(trace->events, "context", ITOP(3));
        mprAddKey(trace->events, "form", ITOP(4));
        mprAddKey(trace->events, "body", ITOP(5));
        mprAddKey(trace->events, "debug", ITOP(5));

        trace->size = HTTP_TRACE_MAX_SIZE;
        trace->formatter = httpDetailTraceFormatter;
        trace->logger = httpWriteTraceLogFile;
        trace->mutex = mprCreateLock();
    }
    return trace;
}
Exemple #13
0
PUBLIC void espDefineAction(HttpRoute *route, cchar *target, void *callback)
{
    EspRoute    *eroute;
    char        *action, *controller;

    assert(route);
    assert(target && *target);
    assert(callback);

    eroute = ((EspRoute*) route->eroute)->top;
    if (target) {
#if DEPRECATED || 1
        /*
            Keep till version 6
         */
        if (scontains(target, "-cmd-")) {
            target = sreplace(target, "-cmd-", "/");
        } else if (schr(target, '-')) {
            controller = ssplit(sclone(target), "-", (char**) &action);
            target = sjoin(controller, "/", action, NULL);
        }
#endif
        if (!eroute->actions) {
            eroute->actions = mprCreateHash(-1, MPR_HASH_STATIC_VALUES);
        }
        mprAddKey(eroute->actions, target, callback);
    }
}
Exemple #14
0
static void testHashScale(MprTestGroup *gp)
{
    MprHash     *table;
    MprKey      *sp;
    char        *str;
    char        name[80], *address;
    int         i;

    table = mprCreateHash(HASH_COUNT, 0);
    assert(mprGetHashLength(table) == 0);

    /*
        All inserts below will insert allocated strings. We must free before
        deleting the table.
     */
    for (i = 0; i < HASH_COUNT; i++) {
        mprSprintf(name, sizeof(name), "name.%d", i);
        address = sfmt("%d Park Ave", i);
        sp = mprAddKey(table, name, address);
        assert(sp != 0);
    }
    assert(mprGetHashLength(table) == HASH_COUNT);

    /*
        Check data entered into the hash
     */
    for (i = 0; i < HASH_COUNT; i++) {
        mprSprintf(name, sizeof(name), "name.%d", i);
        str = mprLookupKey(table, name);
        assert(str != 0);
        address = sfmt("%d Park Ave", i);
        assert(strcmp(str, address) == 0);
    }
}
Exemple #15
0
int maAddGroup(MaAuth *auth, char *group, MaAcl acl, bool enabled)
{
    MaGroup     *gp;

    mprAssert(auth);
    mprAssert(group && *group);

    gp = maCreateGroup(auth, group, acl, enabled);
    if (gp == 0) {
        return MPR_ERR_NO_MEMORY;
    }

    /*
     *  Create the index on demand
     */
    if (auth->groups == 0) {
        auth->groups = mprCreateHash(auth, -1);
    }

    if (mprLookupHash(auth->groups, group)) {
        return MPR_ERR_ALREADY_EXISTS;
    }

    if (mprAddHash(auth->groups, group, gp) == 0) {
        return MPR_ERR_NO_MEMORY;
    }
    return 0;
}
Exemple #16
0
static void setupTrace(Ejs *ejs, HttpTrace *trace, int dir, EjsObj *options)
{
    EjsArray    *extensions;
    EjsObj      *ext;
    HttpTrace   *tp;
    int         i, level, *levels;

    tp = &trace[dir];
    levels = tp->levels;
    if ((level = getNumOption(ejs, options, "all")) >= 0) {
        for (i = 0; i < HTTP_TRACE_MAX_ITEM; i++) {
            levels[i] = level;
        }
    } else {
        levels[HTTP_TRACE_CONN] = getNumOption(ejs, options, "conn");
        levels[HTTP_TRACE_FIRST] = getNumOption(ejs, options, "first");
        levels[HTTP_TRACE_HEADER] = getNumOption(ejs, options, "headers");
        levels[HTTP_TRACE_BODY] = getNumOption(ejs, options, "body");
    }
    tp->size = getNumOption(ejs, options, "size");
    if ((extensions = (EjsArray*) ejsGetPropertyByName(ejs, options, EN("include"))) != 0) {
        if (!ejsIs(ejs, extensions, Array)) {
            ejsThrowArgError(ejs, "include is not an array");
            return;
        }
        tp->include = mprCreateHash(0, 0);
        for (i = 0; i < extensions->length; i++) {
            if ((ext = ejsGetProperty(ejs, extensions, i)) != 0) {
                mprAddKey(tp->include, ejsToMulti(ejs, ejsToString(ejs, ext)), "");
            }
        }
    }
    if ((extensions = (EjsArray*) ejsGetPropertyByName(ejs, options, EN("exclude"))) != 0) {
        if (!ejsIs(ejs, extensions, Array)) {
            ejsThrowArgError(ejs, "exclude is not an array");
            return;
        }
        tp->exclude = mprCreateHash(0, 0);
        for (i = 0; i < extensions->length; i++) {
            if ((ext = ejsGetProperty(ejs, extensions, i)) != 0) {
                mprAddKey(tp->exclude, ejsToMulti(ejs, ejsToString(ejs, ext)), MPR->emptyString);
            }
        }
    }
}
Exemple #17
0
PUBLIC void espInitHtmlOptions(Esp *esp)
{
    char   **op;

    esp->internalOptions = mprCreateHash(-1, MPR_HASH_STATIC_VALUES);
    for (op = internalOptions; *op; op++) {
        mprAddKey(esp->internalOptions, *op, op);
    }
}
Exemple #18
0
void maSetStageData(MaConn *conn, cchar *key, cvoid *data)
{
    MaRequest      *req;

    req = conn->request;
    if (req->requestData == 0) {
        req->requestData = mprCreateHash(conn, -1);
    }
    mprAddHash(req->requestData, key, data);
}
Exemple #19
0
static void testIsTableEmpty(MprTestGroup *gp)
{
    MprHash     *table;

    table = mprCreateHash(0, 0);
    assert(table != 0);

    assert(mprGetHashLength(table) == 0);
    assert(mprGetFirstKey(table) == 0);
    assert(mprLookupKey(table, "") == 0);
}
Exemple #20
0
EdiService *ediCreateService()
{
    EdiService      *es;

    if ((es = mprAllocObj(EdiService, manageEdiService)) == 0) {
        return 0;
    }
    MPR->ediService = es;
    es->providers = mprCreateHash(0, MPR_HASH_STATIC_VALUES);
    addValidations();
    return es;
}
Exemple #21
0
static void addValidations()
{
    EdiService  *es;

    es = MPR->ediService;
    es->validations = mprCreateHash(0, MPR_HASH_STATIC_VALUES);
    ediDefineValidation("boolean", checkBoolean);
    ediDefineValidation("format", checkFormat);
    ediDefineValidation("integer", checkInteger);
    ediDefineValidation("number", checkNumber);
    ediDefineValidation("present", checkPresent);
    ediDefineValidation("date", checkDate);
    ediDefineValidation("unique", checkUnique);
}
Exemple #22
0
MaHttp *maCreateHttp(MprCtx ctx)
{
    MaHttp      *http;

    http = mprAllocObjWithDestructorZeroed(ctx, MaHttp, httpDestructor);
    if (http == 0) {
        return 0;
    }
    mprGetMpr(ctx)->appwebHttpService = http;
    http->servers = mprCreateList(http);
    http->stages = mprCreateHash(http, 0);

#if BLD_FEATURE_MULTITHREAD
    http->mutex = mprCreateLock(http);
#endif

    initLimits(http);

#if BLD_UNIX_LIKE
{
    struct passwd   *pp;
    struct group    *gp;

    http->uid = getuid();
    if ((pp = getpwuid(http->uid)) == 0) {
        mprError(http, "Can't read user credentials: %d. Check your /etc/passwd file.", http->uid);
    } else {
        http->username = mprStrdup(http, pp->pw_name);
    }

    http->gid = getgid();
    if ((gp = getgrgid(http->gid)) == 0) {
        mprError(http, "Can't read group credentials: %d. Check your /etc/group file", http->gid);
    } else {
        http->groupname = mprStrdup(http, gp->gr_name);
    }
}
#else
    http->uid = http->gid = -1;
#endif

#if BLD_FEATURE_SEND
    maOpenSendConnector(http);
#endif
#if BLD_FEATURE_NET
    maOpenNetConnector(http);
#endif
    maOpenPassHandler(http);
    return http;
}
Exemple #23
0
PUBLIC int httpOpenActionHandler()
{
    HttpStage     *stage;

    if ((stage = httpCreateHandler("actionHandler", NULL)) == 0) {
        return MPR_ERR_CANT_CREATE;
    }
    HTTP->actionHandler = stage;
    if ((stage->stageData = mprCreateHash(0, MPR_HASH_STATIC_VALUES)) == 0) {
        return MPR_ERR_MEMORY;
    }
    stage->start = startAction;
    return 0;
}
Exemple #24
0
PUBLIC void httpSetTraceFormatterName(HttpTrace *trace, cchar *name)
{
    HttpTraceFormatter  formatter;

    if (name && smatch(name, "common")) {
        if ((trace->events = mprCreateHash(0, MPR_HASH_STATIC_VALUES)) == 0) {
            return;
        }
        mprAddKey(trace->events, "complete", ITOP(0));
        formatter = httpCommonTraceFormatter;
    } else {
       formatter = httpDetailTraceFormatter;
    }
    httpSetTraceFormatter(trace, formatter);
}
Exemple #25
0
/*
    Path should be a relative path from route->documents to the view file (relative-path.esp)
 */
PUBLIC void espDefineView(HttpRoute *route, cchar *path, void *view)
{
    EspRoute    *eroute;

    assert(path && *path);
    assert(view);

    eroute = ((EspRoute*) route->eroute)->top;
    if (route) {
        path = mprGetPortablePath(path);
    }
    if (!eroute->views) {
        eroute->views = mprCreateHash(-1, MPR_HASH_STATIC_VALUES);
    }
    mprAddKey(eroute->views, path, view);
}
Exemple #26
0
PUBLIC void ecInitLexer(EcCompiler *cp)
{
    ReservedWord    *rp;
    int             size;

    size = sizeof(keywords) / sizeof(ReservedWord);
    if ((cp->keywords = mprCreateHash(size, MPR_HASH_UNICODE | MPR_HASH_STATIC_KEYS | MPR_HASH_STATIC_VALUES)) == 0) {
        return;
    }
    for (rp = keywords; rp->name; rp++) {
#if BIT_CHAR_LEN > 1
        rp->name = amtow(cp->keywords, rp->name, NULL);
#endif
        mprAddKey(cp->keywords, rp->name, rp);
    }
}
Exemple #27
0
static void testIterateHash(MprTestGroup *gp)
{
    MprHash     *table;
    MprKey      *sp;
    char        name[80], address[80];
    cchar       *where;
    int         count, i, check[HASH_COUNT];

    table = mprCreateHash(HASH_COUNT, 0);

    memset(check, 0, sizeof(check));

    /*
        Fill the table
     */
    for (i = 0; i < HASH_COUNT; i++) {
        mprSprintf(name, sizeof(name), "Bit longer name.%d", i);
        mprSprintf(address, sizeof(address), "%d Park Ave", i);
        sp = mprAddKey(table, name, sclone(address));
        assert(sp != 0);
    }
    assert(mprGetHashLength(table) == HASH_COUNT);

    /*
        Check data entered into the table
     */
    sp = mprGetFirstKey(table);
    count = 0;
    while (sp) {
        assert(sp != 0);
        where = sp->data;
        assert(isdigit((int) where[0]) != 0);
        i = atoi(where);
        check[i] = 1;
        sp = mprGetNextKey(table, sp);
        count++;
    }
    assert(count == HASH_COUNT);

    count = 0;
    for (i = 0; i < HASH_COUNT; i++) {
        if (check[i]) {
            count++;
        }
    }
    assert(count == HASH_COUNT);
}
Exemple #28
0
static void setEnv(MaConn *conn)
{
    MaRequest       *req;
    MaResponse      *resp;
    MaStage         *handler;
    MprFileInfo     *info;

    req = conn->request;
    resp = conn->response;
    handler = resp->handler;

    setPathInfo(conn);

    if (resp->extension == 0) {
        resp->extension = getExtension(conn);
    }
    if (resp->filename == 0) {
        resp->filename = makeFilename(conn, req->alias, req->url, 1);
    }

    if ((resp->mimeType = (char*) maLookupMimeType(conn->host, resp->extension)) == 0) {
        resp->mimeType = (char*) "text/html";
    }

    if (!(resp->handler->flags & MA_STAGE_VIRTUAL)) {
        /*
         *  Define an Etag for physical entities. Redo the file info if not valid now that extra path has been removed.
         */
        info = &resp->fileInfo;
        if (!info->valid) {
            mprGetFileInfo(conn, resp->filename, info);
        }
        if (info->valid) {
            mprAllocSprintf(resp, &resp->etag, -1, "%x-%Lx-%Lx", info->inode, info->size, info->mtime);
        }
    }

    if (handler->flags & MA_STAGE_FORM_VARS) {
        req->formVars = mprCreateHash(req, MA_VAR_HASH_SIZE);
        if (req->parsedUri->query) {
            maAddFormVars(conn, req->parsedUri->query, (int) strlen(req->parsedUri->query));
        }
    }
    if (handler->flags & MA_STAGE_ENV_VARS) {
        maCreateEnvVars(conn);
    }
}
Exemple #29
0
PUBLIC void espSetFlashv(HttpConn *conn, cchar *kind, cchar *fmt, va_list args)
{
    EspReq      *req;
    cchar       *msg;

    req = conn->reqData;
    msg = sfmtv(fmt, args);

    if (req->flash == 0) {
        req->flash = mprCreateHash(0, MPR_HASH_STABLE);
    }
    mprAddKey(req->flash, kind, sclone(msg));
    /*
        Create a session as early as possible so a Set-Cookie header can be omitted.
     */
    httpGetSession(conn, 1);
}
Exemple #30
0
PUBLIC void espSetFeedbackv(HttpConn *conn, cchar *kind, cchar *fmt, va_list args)
{
    EspReq      *req;
    cchar       *prior, *msg;

    req = conn->data;
    msg = sfmtv(fmt, args);

    if (req->feedback == 0) {
        req->feedback = mprCreateHash(0, MPR_HASH_STABLE);
    }
    if ((prior = mprLookupKey(req->feedback, kind)) != 0) {
        mprAddKey(req->feedback, kind, sjoin(prior, "\n", msg, NULL));
    } else {
        mprAddKey(req->feedback, kind, sclone(msg));
    }
}