用C语言搓一个小型的服务器,拥有路由解析器(支持MVC架构)
架构讲解
最近做学校专周,用C语言和RIO搓一个Tiny服务器,本身没啥难度,但是是让你返回一个页面。
对于特别习惯前后端分离开发的我来说,头疼,还是给json吧,前端html自己接收。
要求我们实现登录和注册,然后大概的方式是前端对tiny进行请求,tiny进行路由解析后,通过fork创建新的进程,再通过execve(filename, argv, envp)进行一个cgi执行,使用setenv来进行程序上下文的传递
代码语言:c复制void serve_dynamic(int fd, const char *filename, const char *cgiargs)
{
char buf[MAXLINE], *emptylist[] = {NULL};
/* Return first part of HTTP response */
sprintf(buf, "HTTP/1.0 200 OKrn");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server: Tiny Web Serverrn");
Rio_writen(fd, buf, strlen(buf));
if (Fork() == 0)
{ /* Child */ // line:netp:servedynamic:fork
/* Real server would set all CGI vars here */
setenv("QUERY_STRING", cgiargs, 1); // line:netp:servedynamic:setenv
Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */ // line:netp:servedynamic:dup2
Execve(filename, emptylist, environ); /* Run CGI program */ // line:netp:servedynamic:execve
}
Wait(NULL); /* Parent waits for and reaps child */ // line:netp:servedynamic:wait
}
添加描述
但是,按照原来的思路,我要写很多个业务处理程序,很麻烦,有没有简单一点的?
有!那不就是Spring Boot和Spring MVC吗?
添加描述
但是不能用框架,vocal,那怎么办?
没有环境,咱们就创建环境,没有条件,咱们就创建条件!
来说说思路,我们现在在tiny层重写一个路由解析,相当于把tiny服务器当作一个网关,把请求的内容按照我们的约定来重新封装,再通过setenv进行路由信息传递,原来是传参数,那么我们就要改,改为“METHOD URL/?PARAM”的形式
上代码
代码语言:c复制void serve_dynamic(int fd, const char *filename, const char *cgiargs, char uri[8192])
{
char buf[MAXLINE], *emptylist[] = {NULL};
char *url=strdup(uri);
if(strlen(cgiargs)){
strcat(url,"?");
strcat(url,cgiargs);
}
printf("【serve_dynamic-Fork】uri写入环境变量:%sn",url);
/* Return first part of HTTP response */
sprintf(buf, "HTTP/1.0 200 OKrn");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server: Tiny Web Serverrn");
Rio_writen(fd, buf, strlen(buf));
//#if usingFork == 1
printf("【serve_dynamic】尝试打开进程:%s 传入子进程信息如下:%srn",filename,uri);
if (Fork() == 0)
{ /* Child */ // line:netp:servedynamic:fork
/* Real server would set all CGI vars here */
setenv("QUERY_STRING", url, 1); // line:netp:servedynamic:setenv
char *buff=getenv("QUERY_STRING");
/* Extract the two arguments */
printf("【serve_dynamic-Fork】进程%s打开成功!t环境变量取出尝试:%srnrn",filename,buff);
Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */ // line:netp:servedynamic:dup2
Execve(filename, emptylist, environ); /* Run CGI program */ // line:netp:servedynamic:execve
// free(url);
}
Wait(NULL); /* Parent waits for and reaps child */ // line:netp:servedynamic:wait
// free(url);
}
url我们在之前拼接,给你们看看doit-请求处理的代码
代码语言:c复制void doit(int fd)
{
int is_static; // 判断访问的资源是否为静态资源
struct stat sbuf; // todo:
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
rio_t rio;
/* Read request line and headers */
Rio_readinitb(&rio, fd);
if (!Rio_readlineb(&rio, buf, MAXLINE)) // line:netp:doit:readrequest
return;
printf("%s", buf);
sscanf(buf, "%s %s %s", method, uri, version); // line:netp:doit:parserequest
// 如果不是get请求,就拒绝,客户端报异常
printf("ParseURI:%sn", uri);
if (strcasecmp(method, "GET")&&strcasecmp(method, "POST"))
{ // line:netp:doit:beginrequesterr
clienterror(fd, method, "501", "Not Implemented",
"Tiny does not implement this method");
return;
} // line:netp:doit:endrequesterr`
read_requesthdrs(&rio); // line:netp:doit:readrequesthdrs
/* Parse URI from GET request */
is_static = parse_uri(uri, filename, cgiargs); // line:netp:doit:staticcheck
// 如果文件读取失败,客户端报错
if (stat(filename, &sbuf) < 0 && is_static)
{ // line:netp:doit:beginnotfound
printf("文件名:%sn",filename);
clienterror(fd, filename, "404", "Not found",
"Tiny couldn't find this file");
return;
} // line:netp:doit:endnotfound
if (is_static)
{ /* Serve static content */
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
{ // line:netp:doit:readable
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn't read the file");
return;
}
printf("访问静态文件:%s",filename);
// 如果访问的是静态文件,那么对静态文件进行响应的处理
serve_static(fd, filename, sbuf.st_size); // line:netp:doit:servestatic
}
else
{ /* Serve dynamic content */
strcpy(filename,"./api/cgin.cgi");
if(stat(filename, &sbuf) < 0){
clienterror(fd, filename, "403", "Forbidden",
"CGIN框架加载失败");
return;
}
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))
{ // line:netp:doit:executable
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn't run the CGI program");
return;
}
char urlmsg[8910]="";
strcat(urlmsg,method);
strcat(urlmsg," ");
strcat(urlmsg,uri);
// 对交互操作进行响应
serve_dynamic(fd, filename, cgiargs, urlmsg); // line:netp:doit:servedynamic
}
}
那么业务层我们怎么做?
我们可以用哈希来进行映射,C语言哈希怎么办?手搓!一个路由对应一个函数
哈希实现
代码语言:c复制//
// Created by 30398 on 2023/12/30.
//
#ifndef C_HASH_TABLE_H
#define C_HASH_TABLE_H
//使用C语言编写个哈希表
#include <stdio.h>
#include "RequestContext.h"
#include "Response.h"
#define DEFAULT_RouterTable_SIZE 1<<5
int NotFound(RequestContext* requestContext){
return 404;
}
typedef struct {
char* key;
ResponseData* (*run)(RequestContext* requestContext);
struct Entry* nextEntry;
} Entry;
const Entry NOTFOUND_ENTRY={
"404",
NotFound,
NULL
};
typedef struct {
Entry** slots;
int size; //
int count;
}RouterTable;
int isNULLEntry(Entry* entry){
if(entry==NULL) return 1;
if(entry->key==NULL) return 1;
return 0;
}
RouterTable* createRouterTable(int size){
#ifdef DEBUG
printf("[MAIN-Hash]哈希表创建中n");
#endif
RouterTable* RouterTable=malloc(sizeof(RouterTable));
RouterTable->count=0;
RouterTable->size=size;
#ifdef DEBUG
printf("[MAIN-Hash]slots创建中n");
#endif
RouterTable->slots= malloc(sizeof(Entry*)*size);
#ifdef DEBUG
printf("[MAIN-Hash]正在对每一个slot进行初始化n");
#endif
for (int i = 0; i < size; i) {
RouterTable->slots[i]= malloc(sizeof (Entry));
RouterTable->slots[i]->key=NULL;
RouterTable->slots[i]->nextEntry=NULL;
RouterTable->slots[i]->run=NULL;
}
#ifdef DEBUG
printf("[MAIN]哈希对象创建成功!n");
#endif
return RouterTable;
}
void freeRouterTable(RouterTable* RouterTable){
#ifdef DEBUG
printf("[freeRouterTable]RouterTable.slots Begin to %drn", RouterTable->size);
#endif
for (int i = 0; i < RouterTable->size; i) {
#ifdef DEBUG
printf("[freeRouterTable]RouterTable.slots[%d]rn",i);
#endif
Entry* entry=RouterTable->slots[i];
while (entry!=NULL){
Entry* nextEntry=entry->nextEntry;
entry->key=NULL;
entry->run=NULL;
free(entry);
entry=nextEntry;
}
}
free(RouterTable->slots);
free(RouterTable);
}
RouterTable* createDefaultRouterTable(){
#ifdef DEBUG
puts("创建默认哈希对象中...n");
#endif
return createRouterTable(DEFAULT_RouterTable_SIZE);
}
// 计算在哈希表中的slot位置
long getHashSlotByKEY(RouterTable *RouterTable,char* key){
long long h=0;
for(char *s=key;*s!='