Skip to content

Instantly share code, notes, and snippets.

@paranlee
Forked from keiya/http-server.c
Last active May 15, 2023 14:01
Show Gist options
  • Save paranlee/5d212da78c4aad6ddbfd3fa2d55babcc to your computer and use it in GitHub Desktop.
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
#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]&nbsp;<a href='%s/'>%s/</a></li>\n", dirst->d_name, dirst->d_name);
break;
default:
fprintf(out,"<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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 );
}
@paranlee
Copy link
Author

paranlee commented May 15, 2023

If you not want to use libcurl on HTML decode. See

char *
pn_html_unescape (const char *url)
{
    GString *dest;
    const char *src;

    dest = g_string_new ("");
    src = url;

    while (*src != '\0') {
        if (*src == '&') {
            const char *end;
            end = strchr (src, ';');
            src++;

            if (!end)
                goto malformed;

            if (src[0] == '#') {
                int id, n;

                src++;

                if (src[0] == 'x')
                    n = sscanf (src + 1, "%x", &id);
                else
                    n = sscanf (src, "%u", &id);
                if (n != 1)
                    goto malformed;

                dest = g_string_append_unichar (dest, id);
            }
            else {
                struct ref refs[] = {
                    { "amp", "&" },
                    { "lt", "<" },
                    { "gt", ">" },
                    { "nbsp", " " },
                    { "copy", "©" },
                    { "quot", "\"" },
                    { "reg", "®" },
                    { "apos", "'" },
                };
                unsigned i;
                for (i = 0; i < ARRAY_SIZE(refs); i++) {
                    struct ref ref = refs[i];
                    int len = MIN(strlen(ref.name), (size_t)(end - src));
                    if (strncmp (src, ref.name, len) == 0) {
                        dest = g_string_append (dest, ref.value);
                        break;
                    }
                }
            }

            src = end + 1;
        }
        else {
            dest = g_string_append_c (dest, *src);
            src++;
        }
    }

    return g_string_free (dest, FALSE);

malformed:
    return g_string_free (dest, TRUE);
}

See nweb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment