Last active
July 2, 2024 01:40
-
-
Save paralax/6f57e457b5edd7b11aae2988ae8564a0 to your computer and use it in GitHub Desktop.
vtwebd - a very tiny web daemon that servers static content
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 <sys/types.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <netdb.h> | |
#include <pthread.h> | |
#include <signal.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include "vtweb.h" | |
int port; | |
void | |
sig_hdlr(int sig) | |
{ | |
switch(sig) { | |
case SIGPIPE: | |
break; | |
} | |
return; | |
} | |
void | |
usage(char *progname) | |
{ | |
fprintf(stderr, "%s usage: %s -d docdir -p port -t nthreads\n", progname, progname); | |
exit(1); | |
} | |
void * | |
thread_start_listen(void *arg) | |
{ | |
int thread_id = (int)arg; | |
struct servent *serv; | |
int n, ns, s, len, one = 1, *status; | |
char buf[1024]; | |
struct sockaddr_in name; | |
static int rc; | |
s = socket(AF_INET, SOCK_STREAM, 0); | |
if (s < 0) { | |
fprintf(stderr, "error: socket creation.\n"); | |
return; | |
} | |
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) { | |
fprintf(stderr, "setsockopt SO_REUSEADDR error.\n"); | |
return; | |
} | |
if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one))) { | |
fprintf(stderr, "setsockopt SO_REUSEPRT error.\n"); | |
return; | |
} | |
memset(&name, 0, sizeof(struct sockaddr_in)); | |
name.sin_family = AF_INET; | |
name.sin_port = htons(port); | |
len = sizeof(struct sockaddr_in); | |
n = INADDR_ANY; | |
memcpy(&name.sin_addr, &n, sizeof(long)); | |
if (bind(s, (struct sockaddr *) & name, len) < 0) { | |
fprintf(stderr, "error: socket bind.\n"); | |
return; | |
} | |
// printf("started thread %d\n", thread_id); | |
if (listen(s, 5) < 0) { | |
fprintf(stderr, "error: listen.\n"); | |
return; | |
} | |
if ((ns = accept(s, (struct sockaddr *) & name, &len)) < 0) { | |
fprintf(stderr, "error: accept.\n"); | |
return; | |
} | |
/* get the request, handle it */ | |
while ((n = recv(ns, buf, sizeof(buf), 0)) > 0) { | |
/* handle the received content here ... */ | |
handle_req(ns, buf); | |
} | |
/* cleanup: close sockets, kill thread */ | |
close(ns); | |
close(s); | |
pthread_join(threads[thread_id], (void *)&status); | |
/* respawn the thread .. we're done */ | |
rc = pthread_create(&threads[thread_id], NULL, thread_start_listen, arg); | |
if (rc) { | |
printf("ERROR: thread initialization failure.\n"); | |
exit(1); | |
} | |
} | |
int | |
main(int argc, char *argv[]) | |
{ | |
static int ch, t, nthreads = 32; | |
struct servent *se; | |
signal(SIGPIPE, sig_hdlr); | |
#if 0 | |
if ((se = getservbyname("www", "tcp")) == NULL) | |
if ((sp = getservbyname ("http", "tcp")) == NULL) | |
printf("oops ...\n"); | |
printf("se: %s\n", se); | |
port = se->s_port; | |
printf("%s\n", port); | |
#endif | |
while ((ch = getopt(argc, argv, "d:p:t:h")) != -1) { | |
switch(ch) { | |
case 'd': | |
docdir = optarg; | |
break; | |
case 'p': | |
port = atoi(optarg); | |
break; | |
case 't': | |
nthreads = atoi(optarg); | |
break; | |
case 'h': | |
default: | |
usage(argv[0]); | |
break; | |
} | |
} | |
for (; t < nthreads; t++) { | |
static int rc; | |
rc = pthread_create(&threads[t], NULL, thread_start_listen, (void *)t); | |
if (rc) { | |
printf("ERROR: thread initialization failure.\n"); | |
exit(1); | |
} | |
} | |
pthread_exit(NULL); | |
} |
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
all: vtwebd | |
request.o: request.c | |
gcc -g -O2 -c request.c | |
vtwebd.o: main.c | |
gcc -pthread -g -O2 -c main.c | |
vtwebd: vtwebd.o request.o | |
gcc -pthread -g -o vtwebd main.o request.o | |
clean: | |
rm -f *.o *.core vtwebd |
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 <sys/param.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <sys/stat.h> | |
#include <netinet/in.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <pthread.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <time.h> | |
#include "vtweb.h" | |
/* | |
* http://radio.weblogs.com/0111551/stories/2002/08/15/playWithStringsTheWayCc | |
* ProgrammersUsedTo.html | |
*/ | |
int | |
reverse(char *str) | |
{ | |
static int x, l; | |
if (NULL == str) | |
return -1; | |
//no string | |
l = strlen(str) - 1; | |
//get the string length | |
if (1 == l) | |
return 1; | |
for (x = 0; x < l; x++, l--) { | |
str[x] ^= str[l]; //triple XOR Trick | |
str[l] ^= str[x]; //for not using a temp | |
str[x] ^= str[l]; | |
} | |
return 0; | |
} | |
/* http://www.codecomments.com/C/message377951-3.html */ | |
static int | |
endswith(char *phrase, char *searchme) | |
{ | |
reverse(phrase); | |
reverse(searchme); | |
if (0 == strncasecmp(phrase, searchme, strlen(phrase))) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
void | |
err500_error_req(int sock, char *uri) | |
{ | |
char tmp[65535], header[65535]; | |
snprintf(header, sizeof(header), | |
"HTTP/1.0 500\nContent-type: text/html\n\n"); | |
write(sock, header, sizeof(header)); | |
snprintf(tmp, sizeof(tmp), "<h1>Error: 500</h1>The file %s was not found on this server.", uri); | |
write(sock, tmp, strlen(tmp)); | |
return; | |
} | |
void | |
err405_error_req(int sock, char *uri) | |
{ | |
char tmp[65535], header[65535]; | |
snprintf(header, sizeof(header), | |
"HTTP/1.0 405\nContent-type: text/html\n\n"); | |
write(sock, header, sizeof(header)); | |
snprintf(tmp, sizeof(tmp), "<h1>Error: 405</h1>Method Not Allowed"); | |
write(sock, tmp, strlen(tmp)); | |
return; | |
} | |
void | |
err404_error_req(int sock, char *uri) | |
{ | |
char tmp[65535], header[65535]; | |
snprintf(header, sizeof(header), | |
"HTTP/1.0 404\nContent-type: text/html\n\n"); | |
write(sock, header, sizeof(header)); | |
snprintf(tmp, sizeof(tmp), "<h1>Error: 404</h1>The file %s was not found on this server.", uri); | |
write(sock, tmp, strlen(tmp)); | |
return; | |
} | |
void | |
err403_error_req(int sock, char *uri) | |
{ | |
char tmp[65535], header[65535]; | |
snprintf(header, sizeof(header), | |
"HTTP/1.0 403\nContent-type: text/html\n\n"); | |
write(sock, header, sizeof(header)); | |
snprintf(tmp, sizeof(tmp), "<h1>Error: 403</h1>You do not have permission for this request %s", uri); | |
write(sock, tmp, strlen(tmp)); | |
return; | |
} | |
char * | |
gettype(char *input) | |
{ | |
char html[] = ".html"; | |
char htm[] = ".htm"; | |
char gif[] = ".gif"; | |
char jpg[] = ".jpg"; | |
char jpeg[] = ".jpeg"; | |
if (endswith(html, input)) | |
return ("text/html"); | |
else if (endswith(htm, input)) | |
return ("text/html"); | |
else if (endswith(gif, input)) | |
return ("image/gif"); | |
else if (endswith(jpg, input)) | |
return ("image/jpeg"); | |
else if (endswith(jpeg, input)) | |
return ("image/jpeg"); | |
else | |
return ("text/plain"); | |
} | |
#define INET6_ADDRSTRLEN 46 | |
//https://stackoverflow.com/questions/2064636/getting-the-source-address-of-an-incoming-socket-connection | |
void | |
peerip(int s) | |
{ | |
socklen_t len; | |
struct sockaddr_storage addr; | |
char ipstr[INET6_ADDRSTRLEN], *res; | |
int port; | |
len = sizeof addr; | |
getpeername(s, (struct sockaddr *) & addr, &len); | |
//deal with both IPv4 and IPv6: | |
if (addr.ss_family == AF_INET) { | |
struct sockaddr_in *s = (struct sockaddr_in *) & addr; | |
port = ntohs(s->sin_port); | |
inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr); | |
} else { | |
//AF_INET6 | |
struct sockaddr_in6 *s = (struct sockaddr_in6 *) & addr; | |
port = ntohs(s->sin6_port); | |
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr); | |
} | |
printf("%s ", ipstr); | |
return; | |
} | |
void | |
log_req(int sock, char *req, int status) | |
{ | |
time_t rawtime; | |
struct tm *timeinfo; | |
char *timestr; | |
time(&rawtime); | |
timeinfo = localtime(&rawtime); | |
timestr = asctime(timeinfo); | |
timestr[strlen(timestr) - 1] = 0; | |
peerip(sock); | |
printf("[%s] \"%s\" %d\n", timestr, req, status); | |
} | |
void | |
handle_req(int sock, char *req) | |
{ | |
static char *line, *method, *uri, *uritmp, *req_orig; | |
static char fname[65535]; /* XXX */ | |
char contents[65535]; /* XXX */ | |
static int i, fd, n; | |
static struct stat sb; | |
static char header[65535]; | |
line = strtok(req, "\n"); | |
req_orig = strdup(line); | |
req_orig[strlen(req_orig) - 1] = 0; | |
method = strtok(line, " \t"); | |
uri = strtok(NULL, " \t"); | |
if (strcmp("GET", method) != 0) { | |
snprintf(header, sizeof(header), | |
"HTTP/1.0 405 Method Not Allowed\nContent-type: text/html\n\n"); | |
write(sock, header, sizeof(header)); | |
shutdown(sock, SHUT_RDWR); | |
close(sock); | |
log_req(sock, req_orig, 405); | |
return; | |
} | |
if (strcmp("/", uri) == 0) { | |
snprintf(header, sizeof(header), | |
"HTTP/1.0 301 Moved Permanently\nLocation: /index.html\n\n"); | |
write(sock, header, sizeof(header)); | |
shutdown(sock, SHUT_RDWR); | |
close(sock); | |
log_req(sock, req_orig, 301); | |
return; | |
} | |
snprintf(fname, sizeof(fname), "%s%s", docdir, uri); | |
i = stat(fname, &sb); | |
if (i != 0) { | |
if (access(uri, F_OK) != -1) { | |
err403_error_req(sock, uri); | |
log_req(sock, req_orig, 403); | |
} else { | |
err404_error_req(sock, uri); | |
log_req(sock, req_orig, 404); | |
} | |
shutdown(sock, SHUT_RDWR); | |
close(sock); | |
return; | |
} | |
uritmp = strdup(uri); | |
snprintf(header, sizeof(header), | |
"HTTP/1.0 200 OK\nContent-type: %s\n\n", gettype(uritmp)); | |
free(uritmp); | |
write(sock, header, sizeof(header)); | |
fd = open(fname, O_RDONLY, 0666); | |
n = read(fd, contents, sizeof(contents)); | |
write(sock, contents, n); | |
shutdown(sock, SHUT_RDWR); | |
close(fd); | |
close(sock); | |
log_req(sock, req_orig, 200); | |
return; | |
} |
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
#define MAX_NUM_THREADS 512 | |
pthread_t threads[MAX_NUM_THREADS]; | |
void * thread_start_listen(void *arg); | |
void handle_req(int sock, char *req); | |
char *docdir; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment