Data Stoor vir HTTP Bediener in C: Globale Sleutelwaarde Stoor met Hanteerders


Inleiding

In die vorige artikel het ons gekyk na hoe om HTTP liggame te hanteer. Hier implementeer ons ‘n basiese data stoor waar ons dinamies teks waardes teen teks sleutels kan stoor en stel dit dan bloot as HTTP koppelvlakke. Die oorspronklike artikel kan hier gelees word.

Stappe

Skep ./src/cache.h soos volg:

#ifndef CACHE_H
#define CACHE_H
#include <pthread.h>

typedef struct CacheEntry {
    char *key;
    char *value;
    struct CacheEntry *next;
} CacheEntry;

typedef struct Cache {
    CacheEntry **buckets;
    int size;
    pthread_mutex_t lock;
} Cache;

Cache* cache_create(int size);
void cache_set(Cache *cache, const char *key, const char *value);
// gee kopie terug, onthou om geheue te deallokeer met free
char* cache_get(Cache *cache, const char *key);
void cache_delete(Cache *cache, const char *key);
void cache_destroy(Cache *cache);

#endif

Skep ./src/cache.c:

#include "cache.h"
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

static unsigned long hash(const char *str) {
    unsigned long hash = 5381;
    int c;
    while ((c = *str++)) hash = ((hash << 5) + hash) + c;
    return hash;
}

Cache* cache_create(int size) {
    Cache *cache = malloc(sizeof(Cache));
    cache->size = size;
    cache->buckets = calloc(size, sizeof(CacheEntry*));
    pthread_mutex_init(&cache->lock, NULL);
    return cache;
}

void cache_set(Cache *cache, const char *key, const char *value) {
    pthread_mutex_lock(&cache->lock);
    unsigned int idx = hash(key) % cache->size;
    
    // soek sleutel op
    CacheEntry *entry = cache->buckets[idx];
    while (entry) {
        if (strcmp(entry->key, key) == 0) {
            free(entry->value);
            entry->value = strdup(value);
            pthread_mutex_unlock(&cache->lock);
            return;
        }
        entry = entry->next;
    }

    // voeg nuwe item by
    CacheEntry *new_entry = malloc(sizeof(CacheEntry));
    new_entry->key = strdup(key);
    new_entry->value = strdup(value);
    new_entry->next = cache->buckets[idx];
    cache->buckets[idx] = new_entry;
    pthread_mutex_unlock(&cache->lock);
}

char* cache_get(Cache *cache, const char *key) {
    pthread_mutex_lock(&cache->lock);
    unsigned int idx = hash(key) % cache->size;
    CacheEntry *entry = cache->buckets[idx];
    while (entry) {
        if (strcmp(entry->key, key) == 0) {
            char *val = strdup(entry->value);
            pthread_mutex_unlock(&cache->lock);
            return val;
        }
        entry = entry->next;
    }
    pthread_mutex_unlock(&cache->lock);
    return NULL;
}

void cache_destroy(Cache *cache) {
    for (int i = 0; i < cache->size; i++) {
        CacheEntry *entry = cache->buckets[i];
        while (entry) {
            CacheEntry *temp = entry;
            entry = entry->next;
            free(temp->key);
            free(temp->value);
            free(temp);
        }
    }
    free(cache->buckets);
    pthread_mutex_destroy(&cache->lock);
    free(cache);
}

Hier gebruik ons die DJB2 hutsfunksie. Ons gebruik ‘n muteks om te beskerm teen gelyktydige lees en skryf van en na die stoor.

Volgende pas on ./src/server.h se datastruktuur aan om ‘n verwysing na die sleutelwaarde stoor in te sluit:

#ifndef SERVER_H
#define SERVER_H

...
#include "cache.h" // nuwe insluting
...
typedef struct Server {
    ...
    Cache *global_cache; // nuwe veld
} Server;
...
#endif

Volgende pas ons die server_create implementasie in ./src/server.c aan:

...
#include "cache.h" // nuut ingesluit

Server* server_create() {
    ...
    server->global_cache = cache_create(1024); // 1024 spasies
    return server;
}
...
void server_cleanup(Server *server) {
    if (server) {
        server_stop(server);
        ...        
        if (server->global_cache) {
            cache_destroy(server->global_cache);
        }
        free(server);
    }
}
...

Om die stoor funksionaliteit te toets kan ons die volgende hanteerder funksies definieer in ./src/routes.h:

char* handle_cache_set(int client_fd, HTTPRequest *req);
char* handle_cache_get(int client_fd, HTTPRequest *req);

Hierdie funksies kan as volg implementeer word in ./src/routes.c:

...
#include "cache.h"

char* handle_cache_set(int client_fd, HTTPRequest *req) {
    // moet POST wees
    if (strcmp(req->method, "POST") != 0) {
        return "{\"error\": \"invalid operation\"}";
    }

    char body_buffer[2048]; 
    char *body = req_get_body(req, client_fd, body_buffer, sizeof(body_buffer));

    // moet application/x-www-form-urlencoded wees
    char content_type[128];
    get_header_value(req->raw_header, "Content-Type", content_type, sizeof(content_type));
    if (strstr(content_type, "application/x-www-form-urlencoded") == NULL) {
        return "{\"error\": \"invalid mime-type\"}";
    }

    // kry k en v parameters in HTTP liggaam
    char key[128], val[128];
    if (get_query_param(body, "k", key, sizeof(key)) && 
        get_query_param(body, "v", val, sizeof(val))
    ) {
        cache_set(req->server->global_cache, key, val);

        // bou JSON antwoord
        char *key_encoded= json_escape(key);
        char *val_encoded= json_escape(val);

        char response_json[512];
            snprintf(response_json, sizeof(response_json),
                "{"
            "\"status\":\"success\","
            "\"key\":%s,"
            "\"value\":%s"
            "}",
            key_encoded,
            val_encoded
        );

        // bou HTTP antwoord
        char response[512];
        // boue HTTP antwoord
        build_http_response(
            response,
            sizeof(response),
            200, "OK",
            "Content-Type", "application/json",
            "Connection", "close",
            NULL,
            response_json
        );

        // stuur HTTP antwoord
        send(client_fd, response, strlen(response), 0);

        // maak geheue skoon
        free(key_encoded);
        free(val_encoded);

        return NULL;
    }

    return "{\"error\": \"invalid form data\"}";
}

char* handle_cache_get(int client_fd, HTTPRequest *req) {
    char key[128];
    static char response_json[512];

    // moet GET wees
    if (strcmp(req->method, "GET") != 0) {
        return "{\"error\": \"invalid operation\"}";
    }

    // kry die sleutel uit die URL parameters
    if (!get_query_param(req->query, "k", key, sizeof(key))) {
        return "{\"error\": \"Missing key parameter 'k'\"}";
    }

    // haal waarde uit die globale stoor (kopie)
    char *val = cache_get(req->server->global_cache, key);

    if (val == NULL) {
        return "{\"error\": \"Key not found in cache\"}";
    }

    char *key_encoded=json_escape(key);
    char *val_encoded=json_escape(val);

    // bou die JSON antwoord
    snprintf(response_json, sizeof(response_json), 
             "{\"key\": %s, \"value\": %s}", 
             key_encoded, val_encoded);

    free(key_encoded);
    free(val_encoded);

    // deallokeer
    free(val);

    return response_json;
}

Die implementasie volg die standaard HTTP operasie semantiek waar jy ‘n POST operasie gebruik as jy iets op die bediener skep en ‘n GET operasie gebruik as jy iets van die bediener af lees.

Registreer dan die HTTP roetes teenoor hierdie nuwe funksies in ./src/main.c soos volg:

route_add(app_server, "/cache/set", handle_cache_set);
route_add(app_server, "/cache/get", handle_cache_get);

Toets

‘n Waarde kan gestoor word soos volg:

curl \
    -X POST http://localhost:8081/cache/set\
    -H "Content-Type: application/x-www-form-urlencoded"\
    -d 'k=user&v=john doe'

‘n Gestoorde waarde kan verkry word soos volg:

curl "http://localhost:8080/cache/get?k=user"

Opsomming

Ons het hier ‘n basiese sleutelwaarde stoor implementeer. In die volgende artikel sal ons daarna kyk om dit te gebruik om gebruiker sessies te implementeer.


(Afrikaanse artikel)

Comments

Popular Posts