Skip to content

Instantly share code, notes, and snippets.

@yne
Last active August 18, 2024 15:42
Show Gist options
  • Save yne/bc4b7426cbe5f18134772a1f0aa7c6f1 to your computer and use it in GitHub Desktop.
Save yne/bc4b7426cbe5f18134772a1f0aa7c6f1 to your computer and use it in GitHub Desktop.
Modular HTTP server
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define expect(cond) if(!(cond))return printf(__FILE__":%i: expect(" #cond ") failed \n", __LINE__),0
#define HTML5_HDR "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!doctype html><html>"
#define PLAIN_HDR(S) "HTTP/1.1 "S"\r\nContent-Type: text/plain\r\n\r\n"
#define SENDA(S, ARGV...) senda(S, (char*[]){ARGV,NULL})
void senda(int s, char**strings) {
while(*strings)send(s, *strings, strlen(*strings), 0),strings++;
}
#define RECV_LINE(s, line) recv_line(s, line, sizeof(line))
int recv_line(int s, char*line, int line_max){
for(int i=0;i<(line_max-1) && recv(s,&line[i],1,0)==1;i++)
if(line[i-(i>0)]=='\r' && line[i]=='\n')
return i-(i>0)+(line[i-(i>0)]='\0');
return -2;
}
#define RECV_HEADERS(s,line,headers) recv_headers(s, line, sizeof(line), headers, sizeof(headers)/sizeof(*headers))
int recv_headers(int s,char*line, size_t line_max,char**headers, int headers_max){
int nb_headers=0;char*colon;
for(int ret,pos=0;(nb_headers+1<headers_max) && (ret=recv_line(s, line+pos, line_max-pos))>0;pos+=ret+1){
if(!(colon=strchr(line+pos,':')))continue;else colon[0]=colon[1]=0;
for(char*p=line+pos;p<colon;++p)*p=tolower(*p);
headers[nb_headers++]=line+pos;
headers[nb_headers++]=colon+2;
}
return nb_headers;
}
void skip_incoming(int s){
for(char line[512];recv(s, line, sizeof(line), 0) > 0;);
}
#include "file.c"
#include "dev.c"
/*TODO /app/ // VPK + PKG support*/
struct{char*module,*export;void (*handler)(int,char*);} routes[64]= {
{"file","GET", file_get},
{"file","POST",file_post},
{"dev","GET", dev_get},
{"dev","POST",dev_post},
};
void route404(int s, char*path){skip_incoming(s);
SENDA(s, PLAIN_HDR("404 Not Found"), "/*", path, " Not Found");
}
void (*route_get(char*module,char*meth))(int s, char*path){
for(int i=0;routes[i].module;i++)
if(!memcmp(module,routes[i].module,strlen(routes[i].module)) && !strcmp(meth,routes[i].export))
return routes[i].handler;/* return the $module that have the 'http_$meth' export*/
return route404;
}
void route_print(int s){skip_incoming(s);
/* TODO: dynamic fetch of all running modules that have the 'http_$meth' export and print them */
SENDA(s, HTML5_HDR "<body><ul>");
for(int i=0;routes[i].module;i++){
int g = !strcmp("GET",routes[i].export);
SENDA(s, "<li>",g?"<a href=\"":"",g?routes[i].module:"",g?"\">":"", routes[i].module,g?"</a>":""," <kbd>",routes[i].export,"</kbd></li>");
}
SENDA(s, "</ul></body></html>\n");
}
int main(){
int out, in = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(in, SOL_SOCKET, SO_REUSEPORT, &(int[]){1}, sizeof(int));
bind(in, (struct sockaddr *) &((struct sockaddr_in){.sin_family=AF_INET,htons(8080),{INADDR_ANY}}), sizeof(struct sockaddr_in));
listen(in,5);
while (1){
if((out=accept(in, NULL, NULL)) < 0)break;
setsockopt(out, SOL_SOCKET, SO_RCVTIMEO, &(struct timeval){0,100*1000}, sizeof(struct timeval));
char line[256],*tokens[4]={line};
RECV_LINE(out, line);
for(size_t i=0,t=1;i<sizeof(line)&&t<(sizeof(tokens)/sizeof(*tokens));i++)
if(line[i]<=' ')tokens[t++]=line+i+1+(line[i]=0);
if(tokens[1]=='\0' || !strcmp(tokens[1],"/"))
route_print(out);
else
route_get(tokens[1]+1, tokens[0])(out, strchr(tokens[1]+1,'/')?:"");
close(out);
}
close(in);
return 0;
}
#include <dirent.h>
int open_file(char* folder, char* content_dispo){
char path[1024]="ux0:",*fname=(strstr(content_dispo,"filename=\"")?:"filename=\"no.content-disposition.header.txt\"")+10;
*(strrchr(fname,'\"')?:"")='\0';
strcpy(strcpy(path+4, folder)+strlen(folder), fname);
return 100;//open(fname, O_WRONLY | O_CREAT |O_TRUNC, 0666);
}
void file_post(int s, char*path){
char buf[4096],*headers[64],boundary[70]={'\r','\n'};
RECV_HEADERS(s, buf, headers);
SENDA(s, HTML5_HDR, "<body>");
int fd,bound_len=recv_line(s, boundary+2, sizeof(boundary)-2)+2;
for(int nbhdr;(nbhdr=RECV_HEADERS(s, buf, headers))>1;){
for(int i=0;i<nbhdr;i++)
if(!strcmp(headers[i],"content-disposition"))
fd=open_file(path, headers[i+1]);
for(int len=1; len<bound_len && recv(s,&buf[len-1],1,0)==1;len++)
if (boundary[len-1]!=buf[len-1])
write(fd,buf,len),len=0;
close(fd);
}
SENDA(s, "</body></html>");
}
void file_get(int s, char*path){
for(char line[512];recv(s, line, sizeof(line), 0) > 0;);
int fd = open(path,O_RDONLY);
if(fd<0){
SENDA(s, PLAIN_HDR("404 Not Found"), path, "' Not Found");
return;
}
DIR*dirp = fdopendir(fd);
if(!dirp){//regular file
SENDA(s, PLAIN_HDR("200 OK"));
for(char l,line[64];(l=read(fd, line, sizeof(line))) > 0;send(s,line,l,0));
}else{
SENDA(s, HTML5_HDR "<h1>List of '",path,"'</h1><ul>");
SENDA(s, "<form method=post enctype='multipart/form-data'><input name=f type=file multiple><input type=submit value=Upload></form>");
for(struct dirent*ent;dirp && (ent=readdir(dirp));){
if(ent->d_name[0]=='.')continue;
char*suf=ent->d_type==4?"/":"";
SENDA(s, "<li><a href=\"",ent->d_name,suf,"\">",ent->d_name,suf,"</a></li>");
}
SENDA(s, "</ul></html>");
closedir(dirp);
}
close(fd);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment