Sessie Bestuur vir HTTP Bediener in C: Basiese Gebruiker Sessie Implementasie



Inleiding

In die vorige artikel het ons gekyk na 'n globale datastoor. In hierdie artikel bou ons op die vorige werk en implementeer sessie bestuur vir die bediener. Gebruikers kan sessies skep en vernietig sowel as sessie data manipuleer en ontrek. Sekere roetes kan ook verstel word om 'n gebruiker sessie te verwag.

'n Paar toetse word ook aan die einde verskaf om die korrekte operasie en stabiliteit te verseker.

Stappe

Skep src/session.h soos volg:

#ifndef SESSION_H
#define SESSION_H

#include "cache.h" // gebruik datastoor

typedef struct Session {
    char *uuid;
    Cache *store;
    struct Session *next;
} Session;

typedef struct SessionManager {
    Session *sessions;
    pthread_mutex_t lock;
} SessionManager;

// bestuurder funksies
SessionManager* session_manager_create();
void session_manager_destroy(SessionManager *sm);

// lewensiklus funksies
Session* session_create(SessionManager *sm);
Session* session_get(SessionManager *sm, const char *uuid);
void session_destroy(SessionManager *sm, const char *uuid);

// data hantering funksies
void session_set_data(Session *s, const char *key, const char *value);
char* session_get_data(Session *s, const char *key); // Gee 'n kopie terug

// nuts funksies
char* session_generate_uuid();

#endif

Die Session struktuur bevat 'n unieke identifiseerder en gebruik die datastoor wat ons van tevore implementeer het om sleutelwaarde pare te stoor. Die SessionManager struktuur beheer 'n lys van alle aktiewe sessies. Daar is 'n paar lewensiklus funksies wat gebruik word om sessies te skep, te verkry, en te vernietig. Die session_generate_uuid() funksie genereer 'n unieke indentifiseerder vir sessies.

Vir die implementasie van hierdie funksionaliteit kan ./src/session.c soos volg ingevul word:

#include "session.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>

SessionManager* session_manager_create() {
    SessionManager *sm = malloc(sizeof(SessionManager));
    sm->sessions = NULL;
    pthread_mutex_init(&sm->lock, NULL);
    return sm;
}

char* session_generate_uuid() {
    unsigned char bin[16];
    int fd = open("/dev/urandom", O_RDONLY);
    read(fd, bin, 16);
    close(fd);

    char *uuid = malloc(37);
    sprintf(uuid, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
            bin[0], bin[1], bin[2], bin[3], bin[4], bin[5], bin[6], bin[7],
            bin[8], bin[9], bin[10], bin[11], bin[12], bin[13], bin[14], bin[15]);
    return uuid;
}

Session* session_create(SessionManager *sm) {
    Session *s = malloc(sizeof(Session));
    s->uuid = session_generate_uuid();
    s->store = cache_create(16); // Klein hash-tabel vir individuele sessies

    pthread_mutex_lock(&sm->lock);
    s->next = sm->sessions;
    sm->sessions = s;
    pthread_mutex_unlock(&sm->lock);
    return s;
}

Session* session_get(SessionManager *sm, const char *uuid) {
    if (!uuid) return NULL;
    pthread_mutex_lock(&sm->lock);
    Session *curr = sm->sessions;
    while (curr) {
        if (strcmp(curr->uuid, uuid) == 0) {
            pthread_mutex_unlock(&sm->lock);
            return curr;
        }
        curr = curr->next;
    }
    pthread_mutex_unlock(&sm->lock);
    return NULL;
}

void session_set_data(Session *s, const char *key, const char *value) {
    cache_set(s->store, key, value);
}

char* session_get_data(Session *s, const char *key) {
    return cache_get(s->store, key);
}

void session_destroy(SessionManager *sm, const char *uuid) {
    pthread_mutex_lock(&sm->lock);
    Session **curr = &sm->sessions;
    while (*curr) {
        Session *s = *curr;
        if (strcmp(s->uuid, uuid) == 0) {
            *curr = s->next;
            cache_destroy(s->store);
            free(s->uuid);
            free(s);
            pthread_mutex_unlock(&sm->lock);
            return;
        }
        curr = &s->next;
    }
    pthread_mutex_unlock(&sm->lock);
}

Pas ook ./src/server.h aan met 'n nuwe veld vir die sessie bestuurder:

...
#include "session.h" // nuwe insluting

typedef struct Server {
    // ... bestaande velde ...
    Cache *global_cache;
    SessionManager *session_manager; // nuwe veld
} Server;

Volgende pas ons ./src/server.c aan om die sessie bestuurder te initialiseer:

#include "server.h"
#include "util.h"
#include "router.h"
#include "worker.h"
#include "cache.h" // nuut ingesluit
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <limits.h>

Server* server_create() {
    Server *server = malloc(sizeof(Server));
    if (!server) return NULL;

    // initisialiseer sessie bestuurder en globale klas
    server->global_cache = cache_create(1024); // 1024 spasies
    server->session_manager = session_manager_create();
    
    ...
}

...

void server_cleanup(Server *server) {
    if (server) {
        server_stop(server);
        //vernietig sessie bestuurder
        if (server->session_manager) {
            session_manager_destroy(server->session_manager);
        }

        ...
    }
}

Voeg ook die volgende funksie definisies by ./src/util.h:

void req_get_sid(HTTPRequest *req, char *dest, size_t dest_size);
void send_http_chunk(int fd, const char *data, size_t len);

Hier definieer ons 'n funksie req_get_sid() om die sessie identifiseerder van die koekie of van 'n spesifieke HTTP sleutelwaarde opskrif te kry.

Omdat daar 'n baie groot antal sessies mag bestaan implementeer definieer ons die funksie send_http_chunk. Om 'n HTTP liggaam te bou in sulke gevalle kan 'n baie duur operasie wees. Eerder as om die hele HTTP liggaam te konstrueer en dit dan te stuur, kan ons die resultaat in kleiner happies terug stuur. Die HTTP protokol 'n manier manier om data in klein dele (chunks) te stuur in plaas van alles op een slag.

Normaalweg moet 'n webbediener (server) vooraf weet presies hoe groot 'n lêer is (die Content-Length) voordat dit gestuur word. Met chunked encoding hoef die bediener nie die totale grootte vooraf te bereken nie. Dit stuur die data in 'n reeks opeenvolgende blokke.

Implementeer hierdie funksie in ./src/util.c soos volg:

void req_get_sid(HTTPRequest *req, char *dest, size_t dest_size) {
    dest[0] = '\0';

    // probeer die X-SID lees
    get_header_value(req->raw_header, "X-SID", dest, dest_size);
    if (strlen(dest) > 0) return;

    // probeer die koekie lees
    char cookie_header[512];
    get_header_value(req->raw_header, "Cookie", cookie_header, sizeof(cookie_header));
    
    char *id_start = strstr(cookie_header, "session_id=");
    if (id_start) {
        id_start += 11;
        char *id_end = strchr(id_start, ';');
        size_t len = id_end ? (size_t)(id_end - id_start) : strlen(id_start);
        if (len < dest_size) {
            strncpy(dest, id_start, len);
            dest[len] = '\0';
        }
    }
}
void send_http_chunk(int fd, const char *data, size_t len) {
    char header[32];
    int h_len = snprintf(header, sizeof(header), "%zX\r\n", len);
    send(fd, header, h_len, 0);   // stuur lengte in Hex
    send(fd, data, len, 0);       // stuur data
    send(fd, "\r\n", 2, 0);       // stuur skeiding
}

Volgende kan ons roete hanteerder funksie definieer te make met sessie aktiwiteite in ./src/routes.h:

#ifndef ROUTES_H
#define ROUTES_H
#include "router.h"

...

// sessie hanteerder funksies
char* handle_list_sessions(int client_fd, HTTPRequest *req);
char* handle_drop_all_sessions(int client_fd, HTTPRequest *req);
char* handle_session_login(int client_fd, HTTPRequest *req);
char* handle_session_logout(int client_fd, HTTPRequest *req);
char* handle_session_data(int client_fd, HTTPRequest *req);

#endif

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

char* handle_session_login(int client_fd, HTTPRequest *req) {
    Session *s = session_create(req->server->session_manager);
    session_set_data(s, "username", "admin");

    static char response_body[128];
    snprintf(response_body, sizeof(response_body), "{\"session_id\": \"%s\"}", s->uuid);

    char cookie_header[128];
    snprintf(cookie_header, sizeof(cookie_header), "session_id=%s; Path=/; HttpOnly", s->uuid);

    char res_buf[512];
    build_http_response(res_buf, sizeof(res_buf), 200, "OK", 
                        "Set-Cookie", cookie_header,
                        "Content-Type", "application/json", NULL, response_body);
    
    send(client_fd, res_buf, strlen(res_buf), 0);
    return NULL;
}

char* handle_session_logout(int client_fd, HTTPRequest *req) {
    char sid[64];
    req_get_sid(req, sid, sizeof(sid));
    
    if (sid) {
        session_destroy(req->server->session_manager, session_id);
    }

    char res_buf[1024];
    build_http_response(
        res_buf, sizeof(res_buf), 200, "OK",
        "Set-Cookie", "session_id=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly",
        "Content-Type", "application/json",
        "Connection", "close",
        NULL,
        "{\"message\": \"logged out\"}"
    );

    send(client_fd, res_buf, strlen(res_buf), 0);
    return NULL;
}

char* handle_list_sessions(int client_fd, HTTPRequest *req) {
    SessionManager *sm = req->server->session_manager;
    char chunk[1024];

    // stuur die HTTP opskrif
    build_http_response(chunk, sizeof(chunk), 200, "OK",
                        "Content-Type", "application/json",
                        "Transfer-Encoding", "chunked", // Opsioneel, maar goeie praktyk
                        "Connection", "close", NULL, NULL);
    send(client_fd, chunk, strlen(chunk), 0);

    // begin JSON
    send(client_fd, "{\"sessions\": [", 14, 0);

    pthread_mutex_lock(&sm->lock);
    Session *curr = sm->sessions;
    int first = 1;

    while (curr) {
        // bou JSON stuk
        int len = snprintf(chunk, sizeof(chunk), "%s\"%s\"", 
                          first ? "" : ",", curr->uuid);
        
        // stuur JSON stuk
        if (send(client_fd, chunk, len, 0) <= 0) break; 

        first = 0;
        curr = curr->next;
    }
    pthread_mutex_unlock(&sm->lock);

    // sluit JSON af
    send(client_fd, "]}", 2, 0);

    return NULL;
}

char* handle_drop_all_sessions(int client_fd, HTTPRequest *req) {
    SessionManager *sm = req->server->session_manager;
    int count = 0;

    pthread_mutex_lock(&sm->lock);
    Session *curr = sm->sessions;
    while (curr) {
        Session *next = curr->next;
        
        // maak sessie data skoon
        cache_destroy(curr->store);
        free(curr->uuid);
        free(curr);
        
        curr = next;
        count++;
    }
    // maak lys skoon
    sm->sessions = NULL;
    pthread_mutex_unlock(&sm->lock);

    static char response[128];
    snprintf(response, sizeof(response), 
             "{\"status\": \"success\", \"dropped_count\": %d}", count);
    
    return response;
}

Pas nou ./src/main.c aan om die nuwe roetes te registreer:

route_add(app_server, "/session/login", handle_session_login);
route_add(app_server, "/session/logout", handle_session_logout);
route_add(app_server, "/session/list", handle_list_sessions);
route_add(app_server, "/api/session/list", handle_session_data);
route_add(app_server, "/api/session/drop", handle_drop_all_sessions);

Toetse

Nou is daar genoeg HTTP koppelvlakke vir 'n paar basiese toetse.

Toets 1: Koekies

Hier is 'n toets skripsie om HTTP koekies te gebruik om sessies te hanteer:

#!/bin/bash
URL="http://localhost:8081"
COOKIE_FILE="cookies.txt"
rm -f $COOKIE_FILE
curl -i -X GET "$URL/session/login" -c $COOKIE_FILE
cat $COOKIE_FILE
curl -b $COOKIE_FILE "$URL/session/data"
curl -i -X GET "$URL/session/logout" -b $COOKIE_FILE -c $COOKIE_FILE
curl -i "$URL/session/data" -b $COOKIE_FILE
rm -f $COOKIE_FILE

Toets 2: X-SID Opskrifte

Hier is 'n toets om die X-SID HTTP opskrif te gebruik in plaas van HTTP koekies.

#!/bin/bash
URL="http://localhost:8081"
RESPONSE=$(curl -s -X GET "$URL/session/login")
SID=$(echo $RESPONSE | jq -r '.session_id')
if [ "$SID" == "null" ] || [ -z "$SID" ]; then
    echo "Failed to get sesion_id"
    exit 1
fi
curl -s -H "X-SID: $SID" "$URL/session/data" | jq .
curl -i -s "$URL/session/data" | grep "HTTP/1.1"
curl -s -H "X-SID: $SID" "$URL/session/logout" | jq .
curl -i -s -H "X-SID: $SID" "$URL/session/data" | grep "HTTP/1.1"

Toets 3: Swaar Werkslading

Hier gebruik ons die /api/session/drop roete om al die sessies te verwyder. Daarna skep ons 100000 sessies met die bombardier program. As dit klaar is roep ons die /api/session/list funksie en gebruik die jq program om hulle te tel. As alles reg gewerk het behoort jq 'n afvoer te he van 100000

#!/bin/bash
URL=http://localhost:8081
NREQ=100000
NCON=10
echo dropping sessions...
curl -s $URL/api/session/drop|jq '.'
echo performing load test...
bombardier \
        -n $NREQ\
        -c $NCON\
        $URL/session/login
echo getting session count...
curl -s http://localhost:8081/api/session/list|jq '.sessions | length'

Opsomming

In hierdie artikel het ons basiese sessie bestuur met koekies en HTTP sleutelwaarde opskrifte implementeer. 

Comments

Popular Posts