Skip to content

Instantly share code, notes, and snippets.

@siritori
Created July 8, 2012 13:25
Show Gist options
  • Save siritori/3070957 to your computer and use it in GitHub Desktop.
Save siritori/3070957 to your computer and use it in GitHub Desktop.
http-server.c
http-server-fork.c: In function ‘http_receive_request’:
http-server-fork.c:241: warning: implicit declaration of function ‘strdup’
http-server-fork.c:241: warning: assignment makes pointer from integer without a cast
http-server-fork.c:243: warning: initialization makes pointer from integer without a cast
http-server-fork.c: In function ‘sockaddr_print’:
http-server-fork.c:375: warning: implicit declaration of function ‘getnameinfo’
http-server-fork.c:376: error: ‘NI_NUMERICHOST’ undeclared (first use in this function)
http-server-fork.c:376: error: (Each undeclared identifier is reported only once
http-server-fork.c:376: error: for each function it appears in.)
http-server-fork.c:376: error: ‘NI_NUMERICSERV’ undeclared (first use in this function)
http-server-fork.c: In function ‘delete_zombie’:
http-server-fork.c:410: warning: implicit declaration of function ‘wait4’
http-server-fork.c: In function ‘fdopen_sock’:
http-server-fork.c:421: warning: implicit declaration of function ‘fdopen’
http-server-fork.c:421: warning: assignment makes pointer from integer without a cast
http-server-fork.c:425: warning: assignment makes pointer from integer without a cast
#define _USE_BSD
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <netdb.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#define DOCROOT "/public_html"
struct http_header {
char *content_type;
int status;
size_t length;
};
int string_split(char *str, const char del, int *countp, char ***vecp);
void free_string_vector(const int qc, char **vec);
int strcasecmp(const char *s1, const char *s2);
int is_valid_request(const char *line, const int argc, char *argv[]);
size_t filesize_of(const char *path);
char* content_type_of(const char *path);
int http_server(const int portno);
int http_receive_request_and_send_reply(int com);
char* http_receive_request(FILE *in, char *keepalive);
void http_send_reply(FILE *out, const char *filename);
void http_reply_header(FILE *out, const struct http_header *header);
void http_reply_body(FILE *out, FILE *fp, const size_t length);
void tcp_peeraddr_print(int com);
void sockaddr_print(const struct sockaddr *addrp, const socklen_t addr_len);
int tcp_listen_port(const int portno);
void delete_zombie(void);
int fdopen_sock(int sock, FILE **inp, FILE **outp);
int main(int argc, char *argv[])
{
if(argc != 2) {
fprintf(stdout, "Usage: %s portno\n", argv[0]);
return 1;
}
const int portno = strtol(argv[1], 0, 10);
if(http_server(portno) == 0) {
puts("exit on success");
return 0;
} else {
puts("exit on fail");
return 1;
}
}
static int countchr(char *s, char c)
{
int count;
for(count = 0; *s; ++s) {
if(*s == c) ++count;
}
return count;
}
int string_split(char *str, const char del, int *countp, char ***vecp)
{
char **vec;
int count_max, i, len;
char *s, *p;
if(str == 0) return -1;
count_max = countchr(str, del) + 1;
vec = malloc(sizeof(char *) * (count_max + 1));
if(vec == NULL) return -1;
for(i = 0; i < count_max; ++i) {
while(*str == del) ++str;
if(*str == 0) break;
for(p = str; *p != del && *p != 0; ++p) continue;
len = p - str;
s = malloc(len + 1);
if(s == 0) {
for(int j = 0; j < i; ++j) {
free(vec[j]);
vec[j] = 0;
}
return -1;
}
memcpy(s, str, len);
s[len] = 0;
vec[i] = s;
str = p;
}
vec[i] = 0;
*countp = i;
*vecp = vec;
return i;
}
void free_string_vector(const int qc, char **vec)
{
for(int i = 0; i < qc; ++i) {
if(vec[i] == NULL) continue;
free(vec[i]);
}
free(vec);
}
int strcasecmp(const char *s1, const char *s2) {
while(1) {
while(isspace(*s1)) ++s1;
while(isspace(*s2)) ++s2;
if(*s1 == '\0' || *s2 == '\0') break;
if(tolower(*s1) != tolower(*s2)) return 1;
++s1;
++s2;
}
if(*s1 == '\0' && *s2 == '\0') return 0;
else return 0;
}
int http_server(const int portno)
{
const int sock0 = tcp_listen_port(portno);
if(sock0 < 0) return 1;
while(1) {
delete_zombie();
const int com = accept(sock0, 0, 0);
fprintf(stderr, "accepts sock:%d\n", com);
if(com < 0) {
perror("accept");
return 1;
}
const pid_t child_pid = fork();
if(child_pid > 0) {
// parent : continue listen
close(com);
} else if(child_pid == 0) {
// child : communication with client
close(sock0);
tcp_peeraddr_print(com);
return http_receive_request_and_send_reply(com);
} else {
perror("fork");
return 1;
}
}
return 0;
}
#define BUFFERSIZE 1024
int http_receive_request_and_send_reply(const int com)
{
FILE *in, *out;
if(fdopen_sock(com, &in, &out) < 0) {
fprintf(stderr, "fdopen()\n");
return 1;
}
while(1) {
char keepalive_flag = 0;
char *filename = http_receive_request(in, &keepalive_flag);
const char *home = getenv("HOME");
const size_t pathlen = strlen(home) + strlen(DOCROOT) + strlen(filename);
char *path = malloc(pathlen+1);
if(path == NULL) {
fprintf(stderr, "FATAL: out of memory\n");
exit(EXIT_FAILURE);
}
if(snprintf(path, pathlen+1, "%s%s%s", home, DOCROOT, filename) < 0) {
fprintf(stderr, "ERROR: buffer overflow\n");
http_send_reply(out, NULL);
free(filename);
} else {
http_send_reply(out, path);
free(filename);
free(path);
}
if(!keepalive_flag) break;
fprintf(stderr, "[%d] Keep-Alive, continue.\n", getpid());
}
fprintf(stderr, "[%d] Keep-Alive done.\n", getpid());
fclose(in);
fclose(out);
return 0;
}
int is_valid_request(const char *line, const int argc, char *argv[]) {
if(argc != 3) {
fprintf(stderr, "INVALID REQUEST: invalid number of args\n");
return 0;
}
// check method type
if(strcmp("GET", argv[0])) {
fprintf(stderr, "INVALID REQUEST: unrecognized method\n\t%s\n", argv[0]);
return 0;
}
// check file format
if(argv[1][0] != '/') {
fprintf(stderr, "INVALID REQUEST: invalid filename\n\t%s\n", argv[1]);
return 0;
}
// check HTTP version
if(strcmp("HTTP/1.1\r\n", argv[2]) && strcmp("HTTP/1.0\r\n", argv[2])) {
fprintf(stderr, "INVALID REQUEST: unrecognized version\n\t%s\n", argv[2]);
return 0;
}
// security check
if(strchr(line, '<') || strstr(line, "..")) {
fprintf(stderr, "INVALID REQUEST: Dangerous request line\n\t%s\n", line);
return 0;
}
return 1;
}
char* http_receive_request(FILE *in, char *keepalive)
{
char requestline[BUFFERSIZE];
fgets(requestline, BUFFERSIZE, in);
if(ferror(in)) {
fprintf(stderr, "INVALID REQUEST: No request line.\n");
return NULL;
}
int argc;
char **argv;
// split request line
if(string_split(requestline, ' ', &argc, &argv) < 0) {
fprintf(stderr, "INVALID REQUEST: failed to split\n\t%s\n", requestline);
return NULL;
}
// request check
if(!is_valid_request(requestline, argc, argv)) {
return NULL;
}
// default:index.html
if(argv[1][0] == '/' && argv[1][1] == '\0') {
free(argv[1]);
argv[1] = strdup("/index.html");
}
char *ret = strdup(argv[1]);
free_string_vector(argc, argv);
// read requestline
*keepalive = 0;
char rheader[BUFFERSIZE];
while(fgets(rheader, BUFFERSIZE, in)) {
if(rheader[0] == '\r' || rheader[0] == '\n') break;
if(string_split(rheader, ':', &argc, &argv) < 0) {
fprintf(stderr, "INVALID REQUEST: invalid header format\n");
return NULL;
}
// keep-alive check
if(!strcasecmp("connection", argv[0]) &&
!strcasecmp("keep-alive", argv[1])) {
*keepalive = 1;
}
free_string_vector(argc, argv);
}
return ret;
}
size_t filesize_of(const char *path)
{
struct stat s;
stat(path, &s);
return s.st_size;
}
char* content_type_of(const char *path)
{
char *extension = strrchr(path, '.');
if(extension == NULL || extension[1] == '\0') {
return "application/octet-stream";
}
if(!strcmp(extension, ".txt")) return "text/plain;charset=utf8";
if(!strcmp(extension, ".pdf")) return "application/pdf";;
if(!strcmp(extension, ".htm")) return "text/html";
if(!strcmp(extension, ".html")) return "text/html";
if(!strcmp(extension, ".css")) return "text/css";
if(!strcmp(extension, ".png")) return "image/png";
if(!strcmp(extension, ".gif")) return "image/gif";
if(!strcmp(extension, ".jpg")) return "image/jpeg";
if(!strcmp(extension, ".jpeg")) return "image/jpeg";
return "application/octet-stream";
}
void http_send_reply(FILE *out, const char *path)
{
struct http_header header = {
.status = 400,
.length = 0,
.content_type = "text/plain",
};
// bad request
if(path == NULL) {
http_reply_header(out, &header);
return;
}
fprintf(stderr, "[%d] REQUEST : %s\n", getpid(), path);
FILE *fp;
if((fp = fopen(path, "rb")) == NULL) {
// file not found
header.status = 404;
http_reply_header(out, &header);
} else {
// file found
header.status = 200;
header.length = filesize_of(path);
header.content_type = content_type_of(path);
http_reply_header(out, &header);
http_reply_body(out, fp, header.length);
fclose(fp);
}
}
void http_reply_header(FILE *out, const struct http_header *header)
{
char str[128];
switch(header->status) {
case 200:
sprintf(str, "200 OK");
break;
case 404:
sprintf(str, "404 Not Found");
break;
case 400:
sprintf(str, "400 Bad Request");
break;
default:
// program should not reach here!
exit(EXIT_FAILURE);
}
if(header->status == 200) {
fprintf(out,
"HTTP/1.0 %s\r\n"
"Content-Type: %s\r\n"
"Content-Length: %d\r\n\r\n",
str, header->content_type, header->length);
} else {
fprintf(out, "HTTP/1.0 %s\r\n\r\n", str);
}
}
void http_reply_body(FILE *out, FILE *fp, const size_t length)
{
char *buffer = malloc(length);
if(fread(buffer, 1, length, fp) < length) {
fprintf(stderr, "ERROR: failed to read file\n");
return;
}
fwrite(buffer, 1, length, out);
free(buffer);
fprintf(stderr, "[%d] data transfer done\n", getpid());
}
void tcp_peeraddr_print(const int com)
{
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
if(getpeername(com, (struct sockaddr *) &addr, &addr_len) < 0) {
perror("tcp_peeraddr_print");
return;
}
fprintf(stderr, "[%d] connect from <", getpid());
sockaddr_print((struct sockaddr *) &addr, addr_len);
fprintf(stderr, ">\n");
}
void sockaddr_print(const struct sockaddr *addrp, const 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;
fprintf(stderr, "%s:%s", host, port);
}
int tcp_listen_port(const int portno)
{
const int s = socket(PF_INET, SOCK_STREAM, 0);
if(s < 0) {
perror("socket");
return -1;
}
struct sockaddr_in addr = {
.sin_port = htons(portno),
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
};
if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
perror("bind");
fprintf(stderr, "port number %d is already used\n", portno);
return -1;
}
if(listen(s, 5) < 0) {
perror("listen");
close(s);
return -1;
}
return s;
}
void delete_zombie()
{
pid_t pid;
while((pid = wait4(-1, 0, WNOHANG, 0)) > 0) {
fprintf(stderr, "[%d] zombi %d deleted.\n", getpid(), pid);
}
}
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment