如何使用 C 语言处理 http 服务器接收到的 <form> 数据,例如 (key=value&key=value&key=value&...) 并将每个值分配给一个变量?

How to handle <form> data received by http server e.g (key=value&key=value&key=value&...) using C language and assign each value to a variable?

我在微控制器上安装了 http 服务器 运行。它提供一个简短的 html 网页,其中包含一个表单。使用 POST 方法填写表格并单击提交后,我收到如下表格值:

Key1=value1&Key2=value2&Key3=value3&...

接收到的全部数据作为字符串保存在缓冲区中。

问题是:如何通过将每个 key=vale 保存在变量中来处理这些数据。例如:

int key1 = value1 int key2 = value2 int key3 = value3

非常感谢

如果您只对一组固定的键感兴趣,那么您可能需要一个 table 将键与相应的变量相关联。例如:

enum { Key1, Key2, Key3 /* ... */};
struct {
    const char *key;
    int value;
} key_table[KEY_TABLE_SZ] = {
    [Key1] = { "Key1", INT_MIN },
    [Key2] = { "Key2", INT_MIN },
    [Key3] = { "Key3", INT_MIN },
    /* ... */
};

当您解析查询参数字符串并识别键时,您可以查找 table 以将值设置到关联变量中。

const char *key;
const char *value;
char *query_string2 = strdup(query_string);
char *rest = query_string2;
while ((rest = parse_query_string(rest, &key, &value)) != NULL) {
    int i = key_table_find(key);
    if (i == -1) {
        /* ... unknown key ... */
        continue;
    }
    key_table[i].value = strtol(value, 0, 10);
}
free(query_string2);

然后,你感兴趣的值可以通过枚举来索引

if (key_table[Key1] != INT_MIN) {
    /* ... do something with Key1 ... */
}

可以通过调用 strchrstrchrnul 来解析参数字符串。 (如果你的系统缺少 strchrnul,它就像 strchr,除非找不到搜索字符,它 returns 指向 '[=21=]' 末尾的指针字符串而不是 NULL.)

char * parse_query_string(char *rest, const char **key, const char **value) {
    char *p, *q;
    if ((p = rest) == NULL || (q = strchr(p, '=')) == NULL) return NULL;
    *q++ = '[=13=]';
    if (*(rest = strchrnul(q, '&'))) *rest++ = '[=13=]';
    *key = p;
    *value = q;
    return rest;
}

注意枚举值可以初始化为合适的哈希值,以加快查找操作。

enum {
    Key1 = KEY_TABLE_HASH('K', 'e', 'y', '1'),
    Key2 = KEY_TABLE_HASH('K', 'e', 'y', '2'),
    Key3 = KEY_TABLE_HASH('K', 'e', 'y', '3'),
    /* ... */
};

下面是哈希的假设实现。它最多只能处理 5 个字节长度的字符串,但将其扩展到更长的字符串应该相对简单(最多可达系统支持的最大宏嵌套级别)。

#define HASH(...)         HASH_(UP_TO_5(__VA_ARGS__), __VA_ARGS__)
#define HASH_(...)        HASH__(__VA_ARGS__)
#define HASH__(N, ...)    HASH_##N(2166136261U, __VA_ARGS__)

#define HASH_5(H, A, ...) HASH_4(HASH_1(H, A), __VA_ARGS__)
#define HASH_4(H, A, ...) HASH_3(HASH_1(H, A), __VA_ARGS__)
#define HASH_3(H, A, ...) HASH_2(HASH_1(H, A), __VA_ARGS__)
#define HASH_2(H, A, ...) HASH_1(HASH_1(H, A), __VA_ARGS__)
#define HASH_1(H, A)      (((H) ^ (unsigned)(A)) * 16777619U)

#define UP_TO_5(...) UP_TO_5_(__VA_ARGS__, 5, 4, 3, 2, 1)
#define UP_TO_5_(_1, _2, _3, _4, _5, X, ...) X

#define KEY_TABLE_HASH(...) HASH(__VA_ARGS__) % KEY_TABLE_SZ
enum { KEY_TABLE_SZ = 11 };

并且查找功能是使用相同的哈希算法实现的。

int key_table_find(const char *key) {
    unsigned hash = 2166136261U;
    const char *p = key;
    while (*p) {
        hash = HASH_1(hash, *p);
        ++p;
    }
    hash %= KEY_TABLE_SZ;
    if (key_table[hash].key == 0) return -1;
    if (strcmp(key_table[hash].key, key) != 0) return -1;
    return hash;
}

Try it online!

这是我的热门评论。

这是一个带有测试用例的完整实现:​​

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum {
    KEY1,
    KEY2,
    KEY3
};

typedef struct keyPlusValue {
    char *key;
    int value;
} keyPlusValue_t;

keyPlusValue_t symlist[] = {
    [KEY1] = { "key1", 0 },
    [KEY2] = { "key2", 0 },
    [KEY3] = { "key3", 0 },
};

int symcount = sizeof(symlist) / sizeof(symlist[0]);

void
parse_list(const char *str)
{
    char buf[strlen(str) + 1];
    char *bp;
    char *tok;
    char *val;
    keyPlusValue_t *symtry;
    keyPlusValue_t *symok;

    // NOTE: we _must_ create a copy of the string because caller could pass
    // in a constant string and we are going to _write_ into our copy
    strcpy(buf,str);

    bp = buf;
    while (1) {
        // get next "key=val" pair
        tok = strtok(bp,"&");
        if (tok == NULL)
            break;
        bp = NULL;

        // split up pair into "key" and "value"
        val = strchr(tok,'=');
        if (val == NULL) {
            printf("malformed token -- '%s'\n",tok);
            break;
        }
        *val++ = 0;

        // scan symbol/key table looking for match
        symok = NULL;
        for (symtry = symlist;  symtry < &symlist[symcount];  ++symtry) {
            if (strcmp(tok,symtry->key) == 0) {
                symok = symtry;
                break;
            }
        }

        // if no match found -- should not happen but _must_ be checked for
        if (symok == NULL) {
            printf("unknown key -- '%s'\n",tok);
            break;
        }

        // convert text representation of number into int
        symok->value = atoi(val);
    }
}

void
test(const char *str)
{
    keyPlusValue_t *sym;

    printf("\n");
    printf("test: '%s'\n",str);

    parse_list(str);

    for (sym = symlist;  sym < &symlist[symcount];  ++sym)
        printf(" key='%s' val=%d\n",sym->key,sym->value);
}

int
main(void)
{

    test("key1=1&key2=2&key3=3");
    test("key1=2&key2=3&key3=4");
    test("key1=3&key2=4&key3=5");

    return 0;
}