Roetebestuur en URL-dekodering vir HTTP Bediener in C: Hanteerders, Parameters en Addisionele Modularisasie

In webprogrammering kry mens die konsep van ‘n roetehanteerder. Dit bepaal outomaties watter kode uitgevoer word vir ‘n spesifieke roete wat in die HTTP versoek gespesifiseer was. In hierdie artikel implementeer ons roete hantering. Die oorspronklike artikel is beskikbaar hier. Die vorige artikel is beskikbaar hier.

Inleiding

Tipies, wanneer jy ‘n webwerf besoek, neem die adres die volgende vorm aan:

http://a.com/c/d?b=c

Die http:// deel is die protokol, a is die domein, /c/d is die roete, en ?b=c is die vraagparameters. Om dinge makliker te maak, kan ons die roete (/c/d) karteer na ‘n funksie wat die versoek hanteer. Met so ‘n strategie in plek, kan ons die program verder modulariseer.

Om verskillende roetes te hanteer, soos /api/status of /api/add?a=2&b=4, sou dit beter wees as ons die volgende kon skryf:

char* handle_api_status(int client_fd,HTTPRequest *req){
	return "{\"status\": \"running\", \"version\": \"1.0\"}";
}

route_add(app_server, "/api/status", handle_api_status);

Hier het ons ‘n hanteerder funksie handle_api_status wat registreer word teen die roete /api/status. Enige HTTP versoeke na roete /api/status sal dan hanteer word teen hierdie funksie. Op die manier kan ons verskeie funksies opstel en registreer teen verskeie roetes.

Let daarop dat ons in hierdie implementasie nie die HTTP-metode (POSTPUTPATCH, ens.) in ag neem nie. Hoewel dit nuttig kan wees om die metode tydens registrasie te spesifiseer, kan dit op hierdie stadium binne die hanteerderfunksie self hanteer word deur dit in die HTTPRequest parameter na te slaan.”

Roete Implementasie

Om mee te begin skep ons ‘n nuwe koppelvlak ./src/router.h

#ifndef ROUTER_H
#define ROUTER_H

#include "util.h"

struct Server;

typedef char* (*route_handler)(int client_fd,HTTPRequest *req);

typedef struct Route {
    char *path;
    route_handler handler;
    struct Route *next;
} Route;

void route_add(struct Server *server, const char *path, route_handler handler);
route_handler route_get_handler(struct Server *server, const char *path);
void route_cleanup(struct Server *server);

#endif

Hier het ons ‘n vooraf-verklaring van struct Server, ‘n funksiewyser-tipe route_handler, ‘n struct Route tipe wat as ‘n gekoppelde lys gaan dien, en funksies om roetes vir ‘n sekere bediener by te voeg, te verkry, en om al die strukture daarna skoon te maak. Hier volg die implementasie in ./src/router.c:

#include <stdlib.h>
#include <string.h>
#include "server.h"
#include "router.h"

void route_add(struct Server *server, const char *path, route_handler handler) {
    Route *new_route = malloc(sizeof(Route));
    new_route->path = strdup(path);
    new_route->handler = handler;
    new_route->next = server->routes;
    server->routes = new_route;
}

route_handler route_get_handler(struct Server *server, const char *path) {
    Route *curr = server->routes;
    while (curr) {
        if (strcmp(curr->path, path) == 0) return curr->handler;
        curr = curr->next;
    }
    return NULL;
}

void route_cleanup(struct Server *server) {
    Route *curr = server->routes;
    while (curr) {
        Route *next = curr->next;
        free(curr->path);
        free(curr);
        curr = next;
    }
    server->routes = NULL;
}

Die belangrike funksie hier is route_get_handler wat ons dan sal gebruik in ./src/server.c om die hanteerder vir spesifieke roetes op te soek en dan uit te voer.

Nou kan ons ./src/server.c soos volg aanpas:

#include "server.h"
#include "util.h"
...

void server_start(Server *server) {
    ...
    while (server->is_running) {
        ...
        if (client_fd < 0) {
            ...
        }

        // lees versoek opskrifte
        if (read_http_header(client_fd, buf, sizeof(buf)) > 0) {
            HTTPRequest req;
            
            // interpreteer versoek
            if (parse_http_request(buf, &req) == 0) {
                
                // soek roete op
                route_handler handler = route_get_handler(server, req.path);
                
                if (handler) {
                    // voer roete uit 
                    char *body = handler(client_fd,&req);
                    // indien teks terug gevoer was, 200 OK antwoord met teks as liggaam met tipe text/plain
                    if(body != NULL){
    
                        // probeer mimeType vasstel gebasseer op die eerste karakter van handler se afvoer
                        char mimeType[32];
                             if(body[0]=='{')
                                 sprintf(mimeType,"application/json");
                             if(body[0]=='[')
                                 sprintf(mimeType,"application/json");
                        else if(body[0]=='<')
                                 sprintf(mimeType,"text/html");
                        else
                            sprintf(mimeType,"text/plain");
    
                        build_http_response(res_buf, sizeof(res_buf), 200, "OK", 
                                "Content-Type", mimeType,
                                "Connection", "close", NULL, body);
                        send(client_fd, res_buf, strlen(res_buf), 0);
                    }
                } else {
                    // indien roete nie gevind was nie, antwoord met 404 Not Found
                    const char *not_found = "{\"error\": \"Route not found\"}";
                    build_http_response(
                        res_buf,
                        sizeof(res_buf),
                        404, "Not Found", 
                        "Content-Type", "application/json", 
                        "Connection", "close",
                        NULL,
                        not_found
                    );
                    send(client_fd, res_buf, strlen(res_buf), 0);
                }
            }
        }
        close(client_fd);
    }
}

Hier gebruik ons route_get_handler saam met req.path om die hanteerder op te soek. Indien dit nie gevind word nie, stuur ons ‘n HTTP foutboodskap terug. As ons wel die hanteerder kry, voer ons dit uit. Daar is die opsie dat die hanteerder ‘n tekswaarde terugstuur, in so ‘n geval bepaal ons die Content-Type deur na die eerste karakter te kyk. Indien geen teks teruggestuur word nie, is dit die verantwoordelikheid van die hanteerderfunksie om die HTTP antwoord self te bou en te stuur.

Nou kan ons ‘n nuwe koppelvlak ./src/routes.h en implementasie ./src/routes.c skep wat ons dan kan gebruik om mee te toets. Eerstens ./src/routes.h:

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

char* handle_home(int client_fd,HTTPRequest *req);
char* handle_status(int client_fd,HTTPRequest *req);

#endif

En die implementasie lyk dan so:

#include"routes.h"
#include <sys/socket.h>

char* handle_home(int client_fd,HTTPRequest *req) {
	// outomatiese hantering van skryf duer slegs text terug te voer
	return "<h1>Home page</h1>";
}
char* handle_status(int client_fd,HTTPRequest *req){
	return "{\"status\": \"running\", \"version\": \"1.0\"}";
}

Duidelik is die implementering van kode om spesifieke antwoorde te gee nou baie eenvoudiger. Die laaste stap is om die roetes te registreer in ./src/main.c:

#include "routes.h"
...

int main(void) {
    app_server = server_create();
    server_configure(app_server);

    // registreer roetes
    route_add(app_server, "/", handle_home);
    route_add(app_server, "/api/status", handle_status);

    ...

}

Nou kan ons die program weer kompileer en toets soos volg:

$ curl http://localhost:8080/
<h1>Home page</h1>
curl http://localhost:8080/api/status
{"status": "running", "version": "1.0"}

Ekstra Gereedskap

Om beter met roete vraag parameters te werk, omskryf ons die volgende funksies in ./src/util.h :

#ifndef UTIL_H
#define UTIL_H

...

// dekodeer URL-koorde waarde in deur argument te oorskryf
void url_decode(char *str);

// toets of roete parameter spesifiseer is
int has_query_param(const char *query_params, const char *key);

// gegewe 'n sleutel, kry die waarde in query_params
char *get_query_param(const char *query_params, const char *key, char *dest, size_t dest_size);

...

#endif

Hierdie funksies word gebruik om te werk met die roete parameters, d.w.s. die deel na die ? karakter in die roete. Die hele roete waarde is kodeer, b.v. as die roete /api/a b?c = d was sal dit kodeer na /apia%20b?c%20=%20d, waar %20 ‘n spasie voorstel. Die url_decode funksie help om frases so %20 en ander weer terug na hulle oorsproniklike waardes te kry. Hier is die implementasie vir die funksies in ./src/util.c:

#include "util.h"
#include <string.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>

void url_decode(char *str) {
    char *src = str;
    char *dst = str;
    while (*src) {
        if (*src == '+') {
            *dst++ = ' ';
        } else if (*src == '%' && isxdigit(src[1]) && isxdigit(src[2])) {
            char hex[3] = { src[1], src[2], '\0' };
            *dst++ = (char)strtol(hex, NULL, 16);
            src += 2;
        } else {
            *dst++ = *src;
        }
        src++;
    }
    *dst = '\0';
}

int has_query_param(const char *query_params, const char *key) {
    if (!query_params || !key) return 0;

    char search_key[128];
    snprintf(search_key, sizeof(search_key), "%s=", key);

    char *p = strstr(query_params, search_key);
    while (p != NULL) {
        // verseker begin of voorafgegaan met '&'
        if (p == query_params || *(p - 1) == '&') {
            return 1; // Key found
        }
        p = strstr(p + 1, search_key);
    }

    // ondersoek sleutels sonder waardes, (b.v., ?debug&user=1)
    // soek vir sleutel gevolg deur '&' of die einde van die teks
    p = strstr(query_params, key);
    while (p != NULL) {
        if (p == query_params || *(p - 1) == '&') {
            char next = *(p + strlen(key));
            if (next == '&' || next == '\0' || next == '=') {
                return 1;
            }
        }
        p = strstr(p + 1, key);
    }

    return 0;
}

char *get_query_param(const char *query_params, const char *key, char *dest, size_t dest_size) {
    if (!query_params || !key || !dest) return NULL;

    char search_key[128];
    snprintf(search_key, sizeof(search_key), "%s=", key);

    char *p = (char *)strstr(query_params, search_key);
    while (p != NULL) {
        if (p == query_params || *(p - 1) == '&') {
            char *val_start = p + strlen(search_key);
            char *val_end = strchr(val_start, '&');
            
            size_t len = val_end ? (size_t)(val_end - val_start) : strlen(val_start);
            
            if (len >= dest_size) len = dest_size - 1;
            
            strncpy(dest, val_start, len);
            dest[len] = '\0';
            
            url_decode(dest); //dekodeer resultaat
            return dest;
        }
        p = strstr(p + 1, search_key);
    }
    return NULL;
}
...

Met hierdie funksies in plek kan ons nou 'n meer dinamies hanteerder skryf. Voeg die volgende by ./src/routes.h:

char* handle_api_add(int client_fd,HTTPRequest *req);

Implementeer dit soos volg in ./src/routes.c:

...
char* handle_api_add(int client_fd,HTTPRequest *req) {
	// toets dat beide 'a' en 'b' soek parameters verskaf was
	if (!has_query_param(req->query, "a") || !has_query_param(req->query, "b")) {
		return "{\"error\": \"Missing parameters 'a' or 'b'\"}";
	}

	// kry dekodeerde roete vraag parameters in buffers
	char val_a[32], val_b[32];
	get_query_param(req->query, "a", val_a, sizeof(val_a));
	get_query_param(req->query, "b", val_b, sizeof(val_b));

	// voer logika uit
	int a = atoi(val_a);
	int b = atoi(val_b);
	int sum = a + b;

	// bou JSON liggaam
	static char response[512];
	static char response_body[128];
	snprintf(
		response_body,
		sizeof(response_body), 
		"{\"result\": %d, \"a\": %d, \"b\": %d}",
		sum,
		a,
		b
	);

	// bou HTTP antwoord
	build_http_response(
		response,
		sizeof(response),
		200, "OK",
		"Content-Type", "application/json",
		"Connection", "close",
		NULL,
		response_body
	);

	// hier hanteer ons self die terug skryf prosess self
	send(client_fd, response, strlen(response), 0);

	// bediener sal geen skryf werk doen as NULL terug gevoer word nie
	return NULL;
}

In hierdie hanteerder voeg ons twee nommers a en b bymekaar en voer die som terug. Daar word getoets of die a en b roete vraag parameters verskaf was deur van die has_query_param funksie gebruik te maak. Ons stel twee buffers val_a en val_b op en gebruik dan die get_query_param funksie om die parameters te dekodeer en in val_a en val_b in te lees. Die teks waardes wat in die buffers gestoor was word omgeswaai na integrale toe dur van die atoi funksie gebruik te maak. Dan voeg ons die waardes bymekaar, bou die HTTP antwoord liggaam, bou die hele HTTP antwoord, en stuur dit alles dan terug met die send funksie.

Merk op dat aan die einde van hierdie funksie lewer ons NULL. In daardie geval genereer die bediener nie outomaties vir jou ‘n HTTP antwoord nie, wat jou toelaat om maklik outomaties of handmatig ‘n HTTP antwoord te genereer. In hierdie geval genereer ons die antwoord handmatig.

Opsomming

Die artikel beskryf die implementering van 'n modulêre roetehanteerder in C om URL's doeltreffend te bestuur. Ons het ook gekyk na dekodering en ontrekking van roete navraag parameters. In die volgende artikel sal daar gekyk word na liggewigprosesse te gebruik om versoeke gelyktydig te kan hanteer.

Eksterne Skakels


(Afrikaanse artikel)

Comments

Popular Posts