Changed header implementation from hash-table to double linked list

personal/stbuehler/wip
Stefan Bühler 15 years ago
parent f5cf939d5a
commit 47c0acf4c2

@ -2,168 +2,166 @@
#include "base.h"
#include "http_headers.h"
static void _string_free(gpointer p) {
g_string_free((GString*) p, TRUE);
}
static void _string_queue_string_free(gpointer data, gpointer userdata) {
UNUSED(userdata);
g_string_free((GString*) data, TRUE);
}
static void _http_header_free(gpointer p) {
http_header *h = (http_header*) p;
g_queue_foreach(&h->values, _string_queue_string_free, NULL);
g_queue_clear(&h->values);
g_string_free(h->key, TRUE);
g_string_free(h->data, TRUE);
g_slice_free(http_header, h);
}
static http_header* _http_header_new(const gchar *key, size_t keylen) {
static http_header* _http_header_new(const gchar *key, size_t keylen, const gchar *value, size_t valuelen) {
http_header *h = g_slice_new0(http_header);
g_queue_init(&h->values);
h->key = g_string_new_len(key, keylen);
h->data = g_string_sized_new(keylen + valuelen + 2);
h->keylen = keylen;
g_string_append_len(h->data, key, keylen);
g_string_append_len(h->data, CONST_STR_LEN(": "));
g_string_append_len(h->data, value, valuelen);
return h;
}
static void _header_queue_free(gpointer data, gpointer userdata) {
UNUSED(userdata);
_http_header_free((http_header*) data);
}
http_headers* http_headers_new() {
http_headers* headers = g_slice_new0(http_headers);
headers->table = g_hash_table_new_full(
(GHashFunc) g_string_hash, (GEqualFunc) g_string_equal,
_string_free, _http_header_free);
g_queue_init(&headers->entries);
return headers;
}
void http_headers_reset(http_headers* headers) {
g_hash_table_remove_all(headers->table);
g_queue_foreach(&headers->entries, _header_queue_free, NULL);
g_queue_clear(&headers->entries);
}
void http_headers_free(http_headers* headers) {
if (!headers) return;
g_hash_table_destroy(headers->table);
g_queue_foreach(&headers->entries, _header_queue_free, NULL);
g_queue_clear(&headers->entries);
g_slice_free(http_headers, headers);
}
/* Just insert the header (using lokey)
*/
static void header_insert(http_headers *headers, GString *lokey,
const gchar *key, size_t keylen, const gchar *value, size_t valuelen) {
http_header *h = _http_header_new(key, keylen);
g_queue_push_tail(&h->values, g_string_new_len(value, valuelen));
/** just insert normal header, allow duplicates */
void http_header_insert(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) {
http_header *h = _http_header_new(key, keylen, value, valuelen);
g_queue_push_tail(&headers->entries, h);
}
GList* http_header_find_first(http_headers *headers, const gchar *key, size_t keylen) {
http_header *h;
GList *l;
g_hash_table_insert(headers->table, lokey, h);
for (l = g_queue_peek_head_link(&headers->entries); l; l = g_list_next(l)) {
h = (http_header*) l->data;
if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l;
}
return NULL;
}
/** If header does not exist, just insert normal header. If it exists, append (", %s", value) */
void http_header_append(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) {
GString *lokey, *tval;
GList* http_header_find_next(GList *l, const gchar *key, size_t keylen) {
http_header *h;
lokey = g_string_new_len(key, keylen);
g_string_ascii_down(lokey);
h = (http_header*) g_hash_table_lookup(headers->table, lokey);
if (NULL == h) {
header_insert(headers, lokey, key, keylen, value, valuelen);
} else if (NULL == (tval = g_queue_peek_tail(&h->values))) {
g_string_free(lokey, TRUE);
g_queue_push_tail(&h->values, g_string_new_len(value, valuelen));
} else {
g_string_free(lokey, TRUE);
g_string_append_len(tval, ", ", 2);
g_string_append_len(tval, value, valuelen);
for (l = g_list_next(l); l; l = g_list_next(l)) {
h = (http_header*) l->data;
if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l;
}
return NULL;
}
/** If header does not exist, just insert normal header. If it exists, append ("\r\n%s: %s", key, value) */
void http_header_insert(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) {
GString *lokey;
GList* http_header_find_last(http_headers *headers, const gchar *key, size_t keylen) {
http_header *h;
GList *l;
for (l = g_queue_peek_tail_link(&headers->entries); l; l = g_list_previous(l)) {
h = (http_header*) l->data;
if (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen)) return l;
}
return NULL;
}
/** If header does not exist, just insert normal header. If it exists, append (", %s", value) to the last inserted one */
void http_header_append(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) {
GList *l;
http_header *h;
lokey = g_string_new_len(key, keylen);
g_string_ascii_down(lokey);
h = (http_header*) g_hash_table_lookup(headers->table, lokey);
if (NULL == h) {
header_insert(headers, lokey, key, keylen, value, valuelen);
l = http_header_find_last(headers, key, keylen);
if (NULL == l) {
http_header_insert(headers, key, keylen, value, valuelen);
} else {
g_string_free(lokey, TRUE);
g_queue_push_tail(&h->values, g_string_new_len(value, valuelen));
h = (http_header*) l;
g_string_append_len(h->data, CONST_STR_LEN(", "));
g_string_append_len(h->data, value, valuelen);
}
}
/** If header does not exist, just insert normal header. If it exists, overwrite the value */
/** If header does not exist, just insert normal header. If it exists, overwrite the last occurrence */
void http_header_overwrite(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) {
GString *lokey;
GList *l;
http_header *h;
lokey = g_string_new_len(key, keylen);
g_string_ascii_down(lokey);
h = (http_header*) g_hash_table_lookup(headers->table, lokey);
if (NULL == h) {
header_insert(headers, lokey, key, keylen, value, valuelen);
l = http_header_find_last(headers, key, keylen);
if (NULL == l) {
http_header_insert(headers, key, keylen, value, valuelen);
} else {
g_string_free(lokey, TRUE);
g_string_truncate(h->key, 0);
g_string_append_len(h->key, key, keylen);
/* kill old headers */
g_queue_foreach(&h->values, _string_queue_string_free, NULL);
/* new header */
g_queue_push_tail(&h->values, g_string_new_len(value, valuelen));
h = (http_header*) l;
g_string_truncate(h->data, 0);
g_string_append_len(h->data, key, keylen);
g_string_append_len(h->data, CONST_STR_LEN(": "));
g_string_append_len(h->data, value, valuelen);
}
}
gboolean http_header_remove(http_headers *headers, const gchar *key, size_t keylen) {
GString *lokey;
gboolean res;
void http_header_remove_link(http_headers *headers, GList *l) {
_http_header_free(l->data);
g_queue_delete_link(&headers->entries, l);
}
lokey = g_string_new_len(key, keylen);
g_string_ascii_down(lokey);
res = g_hash_table_remove(headers->table, lokey);
g_string_free(lokey, TRUE);
gboolean http_header_remove(http_headers *headers, const gchar *key, size_t keylen) {
GList *l, *lp = NULL;;
gboolean res = FALSE;
for (l = http_header_find_first(headers, key, keylen); l; l = http_header_find_next(l, key, keylen)) {
if (lp) {
http_header_remove_link(headers, lp);
res = TRUE;
lp = NULL;
}
lp = l;
}
if (lp) {
http_header_remove_link(headers, lp);
res = TRUE;
lp = NULL;
}
return res;
}
http_header* http_header_lookup(http_headers *headers, const gchar *key, size_t keylen) {
GString *lokey;
http_header *h;
GList *l;
lokey = g_string_new_len(key, keylen);
g_string_ascii_down(lokey);
h = (http_header*) g_hash_table_lookup(headers->table, lokey);
g_string_free(lokey, TRUE);
return h;
}
http_header* http_header_lookup_fast(http_headers *headers, const gchar *key, size_t keylen) {
GString lokey;
http_header *h;
/* Fake GString */
lokey.str = (gchar*) key;
lokey.len = lokey.allocated_len = keylen;
h = (http_header*) g_hash_table_lookup(headers->table, &lokey);
return h;
l = http_header_find_last(headers, key, keylen);
return NULL == l ? NULL : (http_header*) l->data;
}
gboolean http_header_is(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen) {
http_header *h = http_header_lookup_fast(headers, key, keylen);
GList *iter;
GList *l;
UNUSED(valuelen);
if (!h) return FALSE;
for (iter = g_queue_peek_head_link(&h->values); NULL != iter; iter = g_list_next(iter)) {
if (0 == strcasecmp( ((GString*)iter->data)->str, value )) return TRUE;
for (l = http_header_find_first(headers, key, keylen); l; l = http_header_find_next(l, key, keylen)) {
http_header *h = (http_header*) l->data;
if (h->data->len - (h->keylen + 2) != valuelen) continue;
if (0 == g_ascii_strcasecmp( &h->data->str[h->keylen+2], value )) return TRUE;
}
return FALSE;
}
void http_header_get_fast(GString *dest, http_headers *headers, const gchar *key, size_t keylen) {
http_header *h = http_header_lookup_fast(headers, key, keylen);
GList *iter;
GList *l;
g_string_truncate(dest, 0);
if (!h) return;
for (iter = g_queue_peek_head_link(&h->values); NULL != iter; iter = g_list_next(iter)) {
for (l = http_header_find_first(headers, key, keylen); l; l = http_header_find_next(l, key, keylen)) {
http_header *h = (http_header*) l->data;
if (dest->len) g_string_append_len(dest, CONST_STR_LEN(", "));
g_string_append_len(dest, GSTR_LEN((GString*)iter->data));
g_string_append_len(dest, &h->data->str[h->keylen+2], h->data->len - (h->keylen + 2));
}
}

@ -9,14 +9,19 @@ typedef struct http_headers http_headers;
#include "settings.h"
#define HEADER_VALUE(h) \
(&(h)->data->str[h->keylen + 2])
#define HEADER_VALUE_LEN(h) \
(&(h)->data->str[h->keylen + 2]), ((h)->data->len - (h->keylen + 2))
struct http_header {
GString *key;
GQueue values; /**< queue of GString* */
guint keylen; /** length of "headername" in data */
GString *data; /** "headername: value" */
};
struct http_headers {
/** keys are lowercase header name (GString*), values are http_header* */
GHashTable *table;
GQueue entries;
};
/* strings alweays get copied, so you should free key and value yourself */
@ -32,11 +37,13 @@ LI_API void http_header_insert(http_headers *headers, const gchar *key, size_t k
/** If header does not exist, just insert normal header. If it exists, overwrite the value */
LI_API void http_header_overwrite(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen);
LI_API gboolean http_header_remove(http_headers *headers, const gchar *key, size_t keylen);
LI_API void http_header_remove_link(http_headers *headers, GList *l);
LI_API http_header* http_header_lookup(http_headers *headers, const gchar *key, size_t keylen);
/** Use lowercase keys! */
LI_API http_header* http_header_lookup_fast(http_headers *headers, const gchar *key, size_t keylen);
LI_API GList* http_header_find_first(http_headers *headers, const gchar *key, size_t keylen);
LI_API GList* http_header_find_next(GList *l, const gchar *key, size_t keylen);
LI_API GList* http_header_find_last(http_headers *headers, const gchar *key, size_t keylen);
/** Use lowercase keys! values are compared case-insensitive */
LI_API gboolean http_header_is(http_headers *headers, const gchar *key, size_t keylen, const gchar *value, size_t valuelen);
@ -44,4 +51,8 @@ LI_API gboolean http_header_is(http_headers *headers, const gchar *key, size_t k
/** concats all headers with key with ', ' - empty if no header exists - use lowercase key*/
LI_API void http_header_get_fast(GString *dest, http_headers *headers, const gchar *key, size_t keylen);
INLINE gboolean http_header_key_is(http_header *h, const gchar *key, size_t keylen) {
return (h->keylen == keylen && 0 == g_ascii_strncasecmp(key, h->data->str, keylen));
}
#endif

@ -240,7 +240,7 @@ static action_result core_handle_test(connection *con, gpointer param) {
chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\nevent handler: "));
chunkqueue_append_mem(con->out, backend, strlen(backend));
chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n\r\n--- headers ---\r\n"));
/* chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n\r\n--- headers ---\r\n"));
g_hash_table_iter_init(&iter, con->request.headers->table);
while (g_hash_table_iter_next(&iter, &k, &v)) {
hv = g_queue_peek_head_link(&((http_header*)v)->values);
@ -251,7 +251,7 @@ static action_result core_handle_test(connection *con, gpointer param) {
chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n"));
hv = hv->next;
}
}
}*/
chunkqueue_append_mem(con->out, CONST_STR_LEN("\r\n"));
connection_handle_direct(con);

@ -89,6 +89,7 @@ gboolean request_parse_url(connection *con) {
void request_validate_header(connection *con) {
request *req = &con->request;
http_header *hh;
GList *l;
switch (req->http_version) {
case HTTP_VERSION_1_0:
@ -110,12 +111,14 @@ void request_validate_header(connection *con) {
}
/* get hostname */
hh = http_header_lookup_fast(req->headers, CONST_STR_LEN("host"));
if (hh && hh->values.length != 1) {
l = http_header_find_first(req->headers, CONST_STR_LEN("host"));
if (NULL != l && NULL != http_header_find_next(l, CONST_STR_LEN("host"))) {
/* more than one "host" header */
bad_request(con, 400); /* bad request */
return;
} else if (hh) {
g_string_append_len(req->uri.authority, GSTR_LEN((GString*) g_queue_peek_head(&hh->values)));
} else {
hh = (http_header*) l->data;
g_string_append_len(req->uri.authority, HEADER_VALUE_LEN(hh));
if (!parse_hostname(&req->uri)) {
bad_request(con, 400); /* bad request */
return;
@ -135,13 +138,13 @@ void request_validate_header(connection *con) {
}
/* content-length */
hh = http_header_lookup_fast(req->headers, CONST_STR_LEN("content-length"));
hh = http_header_lookup(req->headers, CONST_STR_LEN("content-length"));
if (hh) {
GString *val = (GString*) g_queue_peek_head(&hh->values);
const gchar *val = HEADER_VALUE(hh);
off_t r;
char *err;
r = str_to_off_t(val->str, &err, 10);
r = str_to_off_t(val, &err, 10);
if (*err != '\0') {
CON_TRACE(con, "content-length is not a number: %s (Status: 400)", err);
bad_request(con, 400); /* bad request */
@ -172,13 +175,13 @@ void request_validate_header(connection *con) {
}
/* Expect: 100-continue */
hh = http_header_lookup_fast(req->headers, CONST_STR_LEN("expect"));
if (hh) {
GList *iter;
l = http_header_find_first(req->headers, CONST_STR_LEN("expect"));
if (l) {
gboolean expect_100_cont = FALSE;
for (iter = g_queue_peek_head_link(&hh->values); NULL != iter; iter = g_list_next(iter)) {
if (0 == strcasecmp( ((GString*)iter->data)->str, "100-continue" )) {
for ( ; l ; l = http_header_find_next(l, CONST_STR_LEN("expect")) ) {
hh = (http_header*) l->data;
if (0 == strcasecmp( HEADER_VALUE(hh), "100-continue" )) {
expect_100_cont = TRUE;
} else {
/* we only support 100-continue */

@ -80,28 +80,15 @@ void response_send_headers(connection *con) {
/* Append headers */
{
GHashTableIter iter;
GString *key;
http_header *header;
GList *valiter;
GList *iter;
gboolean have_date = FALSE, have_server = FALSE;
g_hash_table_iter_init (&iter, con->response.headers->table);
while (g_hash_table_iter_next (&iter, (gpointer*) &key, (gpointer*) &header)) {
if (!header) continue;
valiter = g_queue_peek_head_link(&header->values);
if (!valiter) continue;
do {
g_string_append_len(head, GSTR_LEN(header->key));
g_string_append_len(head, CONST_STR_LEN(": "));
g_string_append_len(head, GSTR_LEN((GString*) valiter->data));
g_string_append_len(head, CONST_STR_LEN("\r\n"));
} while (NULL != (valiter = g_list_next(valiter)));
/* key is lowercase */
if (0 == strcmp(key->str, "date")) have_date = TRUE;
else if (0 == strcmp(key->str, "server")) have_server = TRUE;
for (iter = g_queue_peek_head_link(&con->response.headers->entries); iter; iter = g_list_next(iter)) {
header = (http_header*) iter->data;
g_string_append_len(head, GSTR_LEN(header->data));
if (!have_date && http_header_key_is(header, CONST_STR_LEN("date"))) have_date = TRUE;
if (!have_server && http_header_key_is(header, CONST_STR_LEN("server"))) have_server = TRUE;
}
if (!have_date) {

Loading…
Cancel
Save