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=cDie 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(POST,PUT,PATCH, 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);
#endifHier 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);
#endifEn 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);
...
#endifHierdie 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.


Comments
Post a Comment