-
-
Save paranlee/5d212da78c4aad6ddbfd3fa2d55babcc to your computer and use it in GitHub Desktop.
`gcc http.c -lcurl -o http.o` `sudo apt install libcurl4-openssl-dev -y` [ a simple http server ]feature: directory listing, high-throughput mmap io
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> /* fprintf() */ | |
#include <stdlib.h> /* exit() */ | |
#include <sys/types.h> /* socket(), wait4() */ | |
#include <sys/socket.h> /* socket() */ | |
#include <netinet/in.h> /* struct sockaddr_in */ | |
#include <sys/resource.h> /* wait4() */ | |
#include <sys/wait.h> /* wait4() */ | |
#include <pthread.h> /* pthread_create */ | |
#include <netdb.h> /* getnameinfo() */ | |
#include <string.h> /* strlen() */ | |
#include <sys/stat.h> | |
#include <dirent.h> | |
#include <sys/mman.h> // mmap | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <curl/curl.h> | |
typedef struct request { | |
char method[8]; | |
char *path; | |
} Request; | |
struct http_server_thread_arg { | |
int com; | |
}; | |
char mimetable[][32] = {\ | |
".html", "text/html",\ | |
".txt", "text/plain",\ | |
".js", "application/javascript",\ | |
".css", "text/css",\ | |
".png", "image/png",\ | |
".jpg", "image/jpeg",\ | |
".gif", "image/gif"\ | |
}; | |
struct http_server_thread_arg; | |
extern void http_server( int portno, int ip_version ); | |
extern void http_receive_request_and_send_reply( int com ); | |
extern void *http_server_thread( struct http_server_thread_arg *arg ); | |
void http_send_reply_not_impl( FILE *out ); | |
extern int http_receive_request( FILE *in , Request *request ); | |
extern void http_receive_request_free( Request *request ); | |
void mime_lookup(char *mime, char *filename); | |
extern int http_send_reply( FILE *out , Request *request ); | |
extern void http_send_reply_bad_request( FILE *out ); | |
extern void print_my_host_port_http( int portno ); | |
extern void tcp_peeraddr_print( int com ); | |
extern void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len ); | |
extern int tcp_acc_port( int portno, int ip_version ); | |
extern int fdopen_sock( int sock, FILE **inp, FILE **outp ); | |
extern char *chomp( char *str ); | |
#define DOCROOT "/Users/keiya/GoogleDrive" | |
int | |
main( int argc, char *argv[] ) | |
{ | |
signal( SIGPIPE , SIG_IGN ); | |
int portno, ip_version; | |
if( !(argc == 2 || argc==3) ) { | |
fprintf(stderr,"Usage: %s portno {ipversion}\n",argv[0] ); | |
exit( 1 ); | |
} | |
portno = strtol( argv[1],0,10 ); | |
if( argc == 3 ) | |
ip_version = strtol( argv[2],0,10 ); | |
else | |
ip_version = 4; /* IPv4 by default */ | |
http_server( portno,ip_version ); | |
return 0; | |
} | |
void | |
http_server( int portno, int ip_version ) | |
{ | |
int acc,com ; | |
pthread_t worker ; | |
struct http_server_thread_arg *arg; | |
acc = tcp_acc_port( portno, ip_version ); | |
if( acc<0 ) { | |
perror("tcp_acc_port"); | |
exit( -1 ); | |
} | |
print_my_host_port_http( portno ); | |
while( 1 ) | |
{ | |
// printf("[%d] accepting incoming connections (fd==%d) ...\n",getpid(),acc ); | |
if( (com = accept( acc,0,0 )) < 0 ) | |
{ | |
perror("accept"); | |
exit( -1 ); | |
} | |
// tcp_peeraddr_print( com ); | |
//http_receive_request_and_send_reply( com ); | |
arg = malloc( sizeof(struct http_server_thread_arg) ); | |
if( arg == NULL ) | |
{ | |
perror("malloc()"); | |
exit( -1 ); | |
} | |
arg->com = com; | |
if( pthread_create( &worker, NULL, (void *)http_server_thread, (void *)arg) | |
!= 0 ) | |
{ | |
perror("pthread_create()"); | |
exit( 1 ); | |
} | |
pthread_detach( worker ); | |
} | |
} | |
void * | |
http_server_thread( struct http_server_thread_arg *arg ) | |
{ | |
http_receive_request_and_send_reply( arg->com ); | |
free( arg ); | |
return( NULL ); | |
} | |
#define BUFFERSIZE 1024 | |
void | |
http_receive_request_and_send_reply( int com ) | |
{ | |
FILE *in, *out ; | |
Request request; | |
if( fdopen_sock(com,&in,&out) < 0 ) | |
{ | |
fprintf(stderr,"fdooen()\n"); | |
exit( 1 ); | |
} | |
switch( http_receive_request( in , &request ) ) { | |
case -1: | |
http_send_reply_not_impl( out ); | |
break; | |
case 1: | |
http_send_reply( out , &request ); | |
break; | |
case 0: | |
http_send_reply_bad_request( out ); | |
break; | |
} | |
free(request.path); | |
// printf("[%d] Replied\n",getpid() ); | |
fclose( in ); | |
fclose( out ); | |
} | |
int | |
http_receive_request( FILE *in , Request *request ) | |
{ | |
char requestline[BUFFERSIZE] ; | |
char rheader[BUFFERSIZE] ; | |
if( fgets(requestline,BUFFERSIZE,in) <= 0 ) | |
{ | |
printf("No request line.\n"); | |
return( 0 ); | |
} | |
chomp( requestline ); /* remove \r\n */ | |
// printf("requestline is [%s]\n",requestline ); | |
if( strchr(requestline,'<') || | |
strstr(requestline,"..") ) | |
{ | |
printf("Dangerous request line found.\n"); | |
return( 0 ); | |
} | |
if (strstr(requestline,"GET ") != requestline ) return -1; | |
char *path_starts_at = strchr(requestline,' '); | |
if (path_starts_at == NULL) return 0; | |
path_starts_at++; | |
char *path_ends_at = strrchr(requestline,' '); | |
if (path_ends_at == NULL) return 0; | |
int path_len = strlen(path_starts_at) - strlen(path_ends_at); | |
if ((request->path = malloc(path_len)) == NULL) { | |
perror("malloc"); | |
exit(1); | |
} | |
strncpy(request->path,path_starts_at,path_len+1); | |
*(request->path+path_len) = '\0'; | |
while( fgets(rheader,BUFFERSIZE,in) ) | |
{ | |
chomp( rheader ); /* remove \r\n */ | |
if( strcmp(rheader,"") == 0 ) | |
break; | |
// printf("Ignored: %s\n",rheader ); | |
} | |
return( 1 ); | |
} | |
int | |
http_send_reply( FILE *out, Request *request ) | |
{ | |
char* directory; | |
struct dirent *dirst; | |
DIR *dir; | |
int siz = strlen(DOCROOT) + strlen(request->path) + 1; | |
if ((directory = malloc(siz)) == NULL) { | |
perror("malloc"); | |
exit(1); | |
} | |
CURL *crl; | |
crl = curl_easy_init(); | |
char *url = curl_easy_unescape(crl,request->path,0,0); | |
snprintf(directory,siz,"%s%s",DOCROOT,url); | |
curl_easy_cleanup(crl); | |
struct stat sb; | |
if (stat(directory, &sb) == -1) { | |
fprintf(out,"HTTP/1.0 404 Not Found\r\nContent-Type: text/html\r\n\r\n"); | |
fprintf(out,"<!DOCTYPE html><html><head><meta charset='utf-8'><title>not found</title></head><body>not found.</body></html>\n"); | |
goto noerror; | |
} | |
switch (sb.st_mode & S_IFMT) { | |
case S_IFDIR: | |
if((dir=opendir(directory))==NULL){ | |
fprintf(stderr,"opendir\n"); | |
goto error; | |
} | |
fprintf(out,"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"); | |
fprintf(out,"<!DOCTYPE html><html><head><meta charset='utf-8'><style>body {font-family:monospace;}</style><title>Index of %s</title></head><body><ul>\n",directory); | |
while((dirst = readdir(dir)) != NULL) | |
{ | |
switch (dirst->d_type) { | |
case DT_DIR: | |
fprintf(out,"<li>[DIR] <a href='%s/'>%s/</a></li>\n", dirst->d_name, dirst->d_name); | |
break; | |
default: | |
fprintf(out,"<li> <a href='%s'>%s</a></li>\n", dirst->d_name, dirst->d_name); | |
break; | |
} | |
} | |
fprintf(out,"</ul></body></html>\n"); | |
closedir(dir); | |
break; | |
case S_IFREG: { // caseのあとのassignmentはコンパイルエラーになったのでスコープ | |
int fd = open(directory, O_RDONLY); | |
if (fd < 0) { | |
perror("open"); | |
exit(EXIT_FAILURE); | |
} | |
struct stat fs; | |
if (fstat(fd, &fs) < 0) { | |
perror("fstat"); | |
exit(EXIT_FAILURE); | |
} | |
char mime[32]; | |
mime_lookup(mime,directory); | |
fprintf(out,"HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n",mime); | |
if (fs.st_size > 0) { | |
void *file = mmap(NULL, fs.st_size, PROT_READ, MAP_PRIVATE, fd, 0); | |
close(fd); | |
if (file == MAP_FAILED) { | |
perror("mmap"); | |
exit(EXIT_FAILURE); | |
} | |
if (write(fileno(out),file,fs.st_size) == -1) { | |
munmap(file,fs.st_size); | |
goto error; | |
} | |
else { | |
munmap(file,fs.st_size); | |
} | |
} | |
else { | |
close(fd); | |
} | |
break; | |
} | |
} | |
error: | |
free(directory); | |
curl_free(url); | |
return 0; | |
noerror: | |
free(directory); | |
curl_free(url); | |
return 1; | |
} | |
void | |
mime_lookup(char *mime, char *filename){ | |
int i; | |
char *ext; | |
if ((ext = strrchr(filename, '.')) == NULL) { | |
strcpy(mime,"text/plain"); | |
return; | |
} | |
for (i=0; i < sizeof(mimetable)/sizeof(mimetable[0]); i+=2) { | |
if (strstr(ext,mimetable[i])) { | |
strcpy(mime,mimetable[i+1]); | |
} | |
} | |
} | |
void | |
http_send_reply_bad_request( FILE *out ) | |
{ | |
fprintf(out,"HTTP/1.0 400 Bad Request\r\nContent-Type: text/html\r\n\r\n"); | |
fprintf(out,"<!DOCTYPE html><html><head><meta charset='utf-8'><title>Bad Request</title></head><body>400 Bad Request</body></html>\n"); | |
} | |
void | |
http_send_reply_not_impl( FILE *out ) | |
{ | |
fprintf(out,"HTTP/1.0 501 Not Implemented\r\nContent-Type: text/html\r\n\r\n"); | |
fprintf(out,"<!DOCTYPE html><html><head><meta charset='utf-8'><title>Not Implemented</title></head><body>501 Method Not Implemented</body></html>\n"); | |
} | |
void | |
print_my_host_port_http( int portno ) | |
{ | |
char hostname[100] ; | |
gethostname( hostname,sizeof(hostname) ); | |
hostname[99] = 0 ; | |
// printf("open http://%s(v6):%d/index.html\n",hostname, portno ); | |
} | |
void | |
tcp_peeraddr_print( int com ) | |
{ | |
struct sockaddr_storage addr ; | |
socklen_t addr_len ; /* MacOSX: __uint32_t */ | |
addr_len = sizeof( addr ); | |
if( getpeername( com, (struct sockaddr *)&addr, &addr_len )<0 ) | |
{ | |
perror("tcp_peeraddr_print"); | |
return; | |
} | |
// printf("[%d] connection (fd==%d) from ",getpid(),com ); | |
sockaddr_print( (struct sockaddr *)&addr, addr_len ); | |
printf("\n"); | |
} | |
void | |
sockaddr_print( struct sockaddr *addrp, socklen_t addr_len ) | |
{ | |
char host[BUFFERSIZE] ; | |
char port[BUFFERSIZE] ; | |
if( getnameinfo(addrp, addr_len, host, sizeof(host), | |
port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 ) | |
return; | |
if( addrp->sa_family == PF_INET ) | |
printf("%s:%s", host, port ); | |
else | |
printf("[%s]:%s", host, port ); | |
} | |
#define PORTNO_BUFSIZE 30 | |
int | |
tcp_acc_port( int portno, int ip_version ) | |
{ | |
struct addrinfo hints, *ai; | |
char portno_str[PORTNO_BUFSIZE]; | |
int err, s, on, pf; | |
switch( ip_version ) | |
{ | |
case 4: | |
pf = PF_INET; | |
break; | |
case 6: | |
pf = PF_INET6; | |
break; | |
default: | |
fprintf(stderr,"bad IP version: %d. 4 or 6 is allowed.\n", | |
ip_version ); | |
goto error0; | |
} | |
snprintf( portno_str,sizeof(portno_str),"%d",portno ); | |
memset( &hints, 0, sizeof(hints) ); | |
ai = NULL; | |
hints.ai_family = pf ; | |
hints.ai_flags = AI_PASSIVE; | |
hints.ai_socktype = SOCK_STREAM ; | |
if( (err = getaddrinfo( NULL, portno_str, &hints, &ai )) ) | |
{ | |
fprintf(stderr,"bad portno %d? (%s)\n",portno, | |
gai_strerror(err) ); | |
goto error0; | |
} | |
if( (s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0 ) | |
{ | |
perror("socket"); | |
goto error1; | |
} | |
#ifdef IPV6_V6ONLY | |
if( ai->ai_family == PF_INET6 ) | |
{ | |
on = 1; | |
if( setsockopt(s,IPPROTO_IPV6, IPV6_V6ONLY,&on,sizeof(on)) < 0 ) | |
{ | |
perror("setsockopt(,,IPV6_V6ONLY)"); | |
goto error1; | |
} | |
} | |
#endif /*IPV6_V6ONLY*/ | |
if( bind(s,ai->ai_addr,ai->ai_addrlen) < 0 ) | |
{ | |
perror("bind"); | |
fprintf(stderr,"port number %d can be already used. wait a moment or kill another program.\n", portno ); | |
goto error2; | |
} | |
on = 1; | |
if( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ) < 0 ) | |
{ | |
perror("setsockopt(,,SO_REUSEADDR)"); | |
goto error2; | |
} | |
if( listen( s, 5 ) < 0 ) | |
{ | |
perror("listen"); | |
goto error2; | |
} | |
freeaddrinfo( ai ); | |
return( s ); | |
error2: | |
close( s ); | |
error1: | |
freeaddrinfo( ai ); | |
error0: | |
return( -1 ); | |
} | |
int | |
fdopen_sock( int sock, FILE **inp, FILE **outp ) | |
{ | |
int sock2 ; | |
if( (sock2=dup(sock)) < 0 ) | |
{ | |
return( -1 ); | |
} | |
if( (*inp = fdopen( sock2, "r" )) == NULL ) | |
{ | |
close( sock2 ); | |
return( -1 ); | |
} | |
if( (*outp = fdopen( sock, "w" )) == NULL ) | |
{ | |
fclose( *inp ); | |
*inp = 0 ; | |
return( -1 ); | |
} | |
setvbuf(*outp, (char *)NULL, _IONBF, 0); | |
return( 0 ); | |
} | |
char * | |
chomp( char *str ) | |
{ | |
int len ; | |
len = strlen( str ); | |
if( len>=2 && str[len-2] == '\r' && str[len-1] == '\n' ) | |
{ | |
str[len-2] = str[len-1] = 0; | |
} | |
else if( len >= 1 && (str[len-1] == '\r' || str[len-1] == '\n') ) | |
{ | |
str[len-1] = 0; | |
} | |
return( str ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you not want to use libcurl on HTML decode. See
See nweb