본문 바로가기
컴퓨터 기초/TCP&IP

20.C언어 HTTP 서버 구축하기

by 인생여희 2020. 8. 28.

예제 소스 - http.c

/*
- 소켓 접속
- 데몬화
- syslog를 사용한 로깅
- chroot()를 사용한 보안 향상
- 그에따른 자격증명 지원
- 실행 인자 옵션 해석
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include <ctype.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#define _GNU_SOURCE
#include <getopt.h>
/* 상수 */
#define SERVER_NAME "LittleHTTP"
#define SERVER_VERSION "1.0"
#define HTTP_MINOR_VERSION 0
#define BLOCK_BUF_SIZE 1024
#define LINE_BUF_SIZE 4096
#define MAX_REQUEST_BODY_LENGTH (1024 * 1024)
#define MAX_BACKLOG 5
#define DEFAULT_PORT "80"
/* 데이터 타입 정의 */
//헤더 필드
struct HTTPHeaderField{
char *name;
char *value;
//링크드 리스트/
//하나의 HTTPHeaderField가 헤더 필드 한개를 표현한다.
struct HTTPHeaderField *next;
};
//http 요청 구조체
struct HTTPRequest {
int protocol_minor_version; //요청에 사용된 http 버전
char *method; //요청 메소드 (모두 대문자)
char *path; //요청경로
struct HTTPHeaderField *header; //HTTP 헤더
char *body; //엔티티 본문
long length; //엔티티 본문 길이
};
//파일 정보
struct myFileInfo {
char *path;
long size;
int ok;
};
/* 함수 프로토콜 */
static void setup_environment(char *root , char *user , char *group);
static void detach_children(void);
static void noop_handler(int sig);
static void become_demon(void);
static int listen_socket(char *port);
static void server_main(int sever, char *docroot);
typedef void (*sighandler_t)(int);
static void install_signal_handler(void);
static void trap_signal(int sig, sighandler_t handler);
static void signal_exit(int sig);
static void service(FILE *in , FILE *out, char *docroot);
static struct HTTPRequest* read_request(FILE *in);
static void read_request_line(struct HTTPRequest *req, FILE *in);
static struct HTTPHeaderField* read_header_field(FILE *in);
static char* upcase(char *str);
static void free_request(struct HTTPRequest *req);
static long content_length(struct HTTPRequest *req);
static char* lookup_header_field_value(struct HTTPRequest *req , char *name);
static void response_to(struct HTTPRequest *req, FILE *out , char *docroot);
static void do_file_response(struct HTTPRequest *req , FILE *out , char *docroot);
static void method_not_allowed(struct HTTPRequest *req, FILE *out);
static void not_implemented(struct HTTPRequest *req , FILE *out);
static void not_found(struct HTTPRequest *req , FILE *out);
static void output_common_header_fields(struct HTTPRequest *req, FILE *out, char *status);
static struct myFileInfo* get_fileinfo(char *docroot , char *urlpath);
static char* build_fspath(char *docroot, char *path);
static void free_fileinfo(struct myFileInfo *info);
static char* guess_content_type(struct myFileInfo *info);
static void* xmalloc(size_t sz);
static void log_exit(const char *fmt, ...);
//커멘드 라인 인자 옵션
#define USAGE "Usage : %s [--port=n] [--chroot -- user==u --group=g] [--debug] <docroot>\n"
static int debug_mode = 0;
static struct option longopts[] = {
{"debug", no_argument, &debug_mode, 1},
{"chroot", no_argument, NULL, 'c'},
{"user", required_argument, NULL, 'u'},
{"group", required_argument, NULL, 'g'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{0,0,0,0}
};
int main(int argc, char * argv[]) {
int server_fd;
char *port = NULL;
char *docroot;
int do_chroot = 0;
char *user = NULL;
char *group = NULL;
int opt;
while ((opt = getopt_long(argc, argv, "" , longopts , NULL)) != -1) {
switch (opt) {
case 0:
break;
case 'c':
do_chroot = 1;
break;
case 'u':
user = optarg;
break;
case 'g':
group = optarg;
break;
case 'p':
port = optarg;
break;
case 'h':
fprintf(stdout, USAGE , argv[0]);
exit(0);
break;
case '?':
fprintf(stdout, USAGE , argv[0]);
exit(1);
break;
}
}
if (optind != argc -1) {
fprintf(stderr , USAGE , argv[0]);
exit(1);
}
docroot = argv[optind];
if (do_chroot) {
setup_environment(docroot, user, group);
docroot = "";
}
//시그널 처리 - 소켓연결 종료되면 log_exit 호출
//필요한 신호 포착
install_signal_handler();
server_fd = listen_socket(port);
if (!debug_mode) {
openlog(SERVER_NAME, LOG_PID|LOG_NDELAY, LOG_DAEMON);
become_demon();
}
server_main(server_fd, docroot);
/*
//문서 루투 경로의 실행인자가 필요하다.
//디렉터리 여부를 체크하기 위해서 stat() 를 사용해도 된다.
if (argc != 2) {
fprintf(stderr, "usage: %s <docroot>\n" , argv[0]);
exit(1);
}
//시그널 처리 - 소켓연결 종료되면 log_exit 호출
//필요한 신호 포착
install_signal_handler();
//표준입력과 표준출력 지정해서 처리 - 표준입력, 표준출력, root 경로 넣어주기
service(stdin, stdout, (char*)argv[1]);
*/
return 0;
}
static void
setup_environment(char *root, char *user, char *group)
{
struct passwd *pw;
struct group *gr;
if (!user || !group) {
fprintf(stderr, "use both of --user and --group\n");
exit(1);
}
gr = getgrnam(group);
if (!gr) {
fprintf(stderr, "no such group: %s\n", group);
exit(1);
}
if (setgid(gr->gr_gid) < 0) {
perror("setgid(2)");
exit(1);
}
if (initgroups(user, gr->gr_gid) < 0) {
perror("initgroups(2)");
exit(1);
}
pw = getpwnam(user);
if (!pw) {
fprintf(stderr, "no such user: %s\n", user);
exit(1);
}
chroot(root);
if (setuid(pw->pw_uid) < 0) {
perror("setuid(2)");
exit(1);
}
}
static void become_demon(void){
int n;
//root 디렉터리에 chdir 한다.
//파일 시스템이 마운트 해제할 수 없게 되는것을 방지하기 위함
//프로세스가 current directory로 사용하고 있는 파일 시스템은 마운트 해제 할 수 없으므로
//장시간 실행되는 데몬은 가급적 루트 디렉토리로 이동해야 한다.
if (chdir("/") < 0) {
log_exit("child(2) failed :%s" , strerror(errno));
}
//표준 입출력을 /dev/null 에 연결한다.
//무심코 표준 입출력을 사용했을때 에러가 발생하지 않도록 하기 위함
freopen("/dev/null" , "r" , stdin);
freopen("/dev/null" , "w" , stdout);
freopen("/dev/null" , "w" , stderr);
n = fork();
if (n < 0) {
log_exit("fork(2) failed :%s" , strerror(errno));
}
//제어단말 잘라낸다.
if (n != 0) {
_exit(0); //부모프로세스는 종료
}
if (setsid() < 0) {
log_exit("setsid(2) failed :%s" , strerror(errno));
}
}
//시그널 처리 -
//네트워크에서 소켓 연결은 갑자기 끊어질 수 있다.
//소켓이 끊어지면 xinetd에서 시작한 경우 SIGPIPE가 전달 된다.
static void install_signal_handler(void){
trap_signal(SIGPIPE, signal_exit);
}
//시그널 처리 2
static void trap_signal(int sig, sighandler_t handler){
//시그널 정보 구조체
struct sigaction act;
//시그널 핸들러 지정
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
//SA_RESTART
//- 시그널 핸들러에 의해 중지된 시스템 호출을 자동적으로 재시작한다.
act.sa_flags = SA_RESTART;
//sigaction : 성공하면 '0', 에러가 발생하면 '-1'을 리턴한다.
if(sigaction(sig, &act, NULL) < 0){
log_exit("sigaction() faile %s" , strerror(errno));
}
}
//시그널 처리 3
static void signal_exit(int sig){
log_exit("exit by signal %d" , sig);
}
//HTTP 요청 처리 + 응답 처리
static void service(FILE *in , FILE *out, char *docroot){
struct HTTPRequest *req;
//in 스트림에서 http 요청을 읽고 httpRequest 구조체의 포인터에 저장하고 반환
req = read_request(in);
//http 요청 req에 대한 응답을 두번째 인자에게 넣는다.
response_to(req, out, docroot);
free_request(req);
}
static void detach_children(void){
//sigaction 을 사용해서 wait를 안할 것이라고 커널에 선언.
struct sigaction act;
act.sa_handler = noop_handler;
sigemptyset(&act.sa_mask);
//커널은 이제 더는 wait() 할일이 없다는 옵션 - 자식 프로세스를 좀비프로세스로 만들지 않음
//주의 : 그 이후는 wait() 가 에러가 된다.
act.sa_flags = SA_RESTART | SA_NOCLDWAIT;
if (sigaction(SIGCHLD, &act , NULL) < 0) {
log_exit("sigaction() failed: %s", strerror(errno));
}
}
static void
noop_handler(int sig)
{
;
}
//소켓 생성 -> 바인딩 -> 대기
//소켓 생성 -> 바인딩 -> 대기는 프로세스당 1회 호출된다.
static int listen_socket(char *port){
//주소 정보 구조체
struct addrinfo hints, *res, *ai;
int err;
//주소 정보 구조체 메모리 할당 + 초기화
memset(&hints , 0 , sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
//요청자 정보 얻어오기?
err = getaddrinfo(NULL, port , &hints , &res);
if(err != 0){
log_exit(gai_strerror(err));
}
for (ai = res; ai; ai = ai->ai_next) {
int sock;
//소켓 생성
sock = socket(ai->ai_family , ai->ai_socktype , ai->ai_protocol);
if (sock < 0) {
continue;
}
//바인딩
if (bind(sock , ai->ai_addr, ai->ai_addrlen) < 0) {
close(sock);
continue;
}
//대기
if (listen(sock , MAX_BACKLOG) < 0) {
close(sock);
continue;
}
freeaddrinfo(res);
return sock;
}
log_exit("failed to listen socket");
return -1; /* 안옴.. */
}
//accept 새로운 소켓을 여러개 반환한다.
static void server_main(int sever_fd, char *docroot){
for (; ; ) {
//요청자 주소 정보 구조체 - sockaddr 사용을 위해서
//sockaddr에는 ip어드레스와 포트 번호를 가지고 있음
struct sockaddr_storage addr;
socklen_t addrlen = sizeof addr;
int sock;
int pid;
//연결이 완료된 새로운 소켓 반환 -- 여러개가됨..
sock = accept(sever_fd , (struct sockaddr*)&addr , &addrlen);
if (sock < 0) {
log_exit("accept(2) failed : %s" , strerror(errno));
}
//fork 후 accept = 프리포크 서버
//accept 후 fork = 병형서버
//자식 프로세스 생성
//복수 접속을 병렬 처리
pid = fork();
if (pid < 0) {
exit(3);
}
//자식 프로세스
if (pid == 0) {
FILE *inf = fdopen(sock , "r");
FILE *outf = fdopen(sock, "w");
//자식 프로세스에서 서비스 작동
service(inf, outf, docroot);
exit(0);
}
//부모 프로세스에서 소켓 닫기 - 부모에서 close 하지 않으면
//클라이언트는 계속 기다림
close(sock);
}
}
//스트림에서 http요청을 읽고 이를 struct httpRequest로 만드는 함수인 read_request
static struct HTTPRequest* read_request(FILE *in){
struct HTTPRequest *req;
struct HTTPHeaderField *h;
//struct HTTPRequest 구조체 메모리 할당
req = xmalloc(sizeof(struct HTTPRequest));
//http 요청의 첫번째 줄에 해당하는 요청라인
//GET /path/ to / file HTTP/1.1 을 읽고 파싱해서 첫 번째 인자인
//struct HTTPRequest 에 기제한다.
read_request_line(req, in);
//이어서 read_header_field를 사용하여 요청 헤더를 한 개씩 읽어
//struct HTTPHeaderField를 만들어 링크드 리스트인 header 필드에 저장한다.
//read_header_field 는 헤더를 전부 읽었을때 NULL을 반환한다.
req->header = NULL;
while (1) {
h = read_header_field(in);
if (h == NULL) {
printf("read_header_field == NULL ! \n");
break;
}
h->next = req->header;
req->header = h;
}
req->length = content_length(req);
//길이가 일정 크기 이상이면 에러
if (req->length != 0) {
if (req->length > MAX_REQUEST_BODY_LENGTH) {
log_exit("request body too long");
}
//request body 메모리 할당
req->body = xmalloc(req->length);
//표준 입력으로 들어온 request body 내용 읽는다.
if (fread(req->body, req->length, 1, in) < 1) {
log_exit("failed to read request body");
}
} else {
req->body = NULL;
printf("read_request함수 req->body는 NULL! \n\n");
}
return req;
}
//커멘드 라인에서 표준 입력(GET /cat.c HTTP/1.0)을 읽어서 HTTPRequest 구조체 리턴
static void read_request_line(struct HTTPRequest *req, FILE *in){
char buf[LINE_BUF_SIZE];
char *path , *p;
//표준 입출력에서 한줄 읽기
if (!fgets(buf, LINE_BUF_SIZE , in)) {
log_exit("no request line");
}
//문자열에서 특정 문자가 한개 있는지 검사
//첫번째 매개 변수 buf의 문자열에서 두번째 매개변수로 들어온 ' ' 가 존재하는지 검사하고
//문자가 존재하면 해당 문자가 존재하는 곳의 포인터를 반환. 존재하지 않으면 널포인터 반환
//p char * " /cat.c HTTP/1.0\n" 0x00007ffeefbfe483
//buf char [4096] "GET /cat.c HTTP/1.0\n"
//참고 : p 포인터는 buf가 있는 주소를 가르킨다.
p = strchr(buf, ' ');
//printf("p : %p \n" , p);
//printf("buf : %p \n" , buf);
if (!p) log_exit("parse error on request line (1): %s", buf);
// ' ' 부분을 \0으로 치환 후 char 크기만큼 한칸 이동
*p++ = '\0';
//(포인터 주소 - 포인터 주소) = 두 주소 사이에 대상이 몇개 있는지 계산
//char 형이므로 결과값은 4 : 'GET '
//unsigned long t = (p - buf) /sizeof(char);
//printf("%lu \n" , t); //4
//*method 변수를 위해 4바이트 만큼 메모리 생성
req->method = xmalloc(p - buf);
//4바이트 메모리 method 변수에 buf의 데이터중 0번째 부터 4번째 즉, 4바이트 데이터만 복사
strcpy(req->method, buf);
char *upcaseResult = upcase(req->method);
req->method = upcaseResult;
//path = char * "/cat.c HTTP/1.0\n" 0x00007ffeefbfe424
path = p;
//p = char * " HTTP/1.0\n" 0x00007ffeefbfe42a
p = strchr(path, ' ');
if (!p) log_exit("parse error on request line (2): %s", buf);
//현재 가리키고 있는 위치에 \0 을 대입하고 포인터를 한칸 이동
// ' ' -> '\0'
*p++ = '\0';
//GET /cat.c HTTP/1.0
//포인터 - 포인터 = 두 주소 사이에 대상이 몇개 있는지 계산
//p = char * "HTTP/1.0\n" 0x00007ffeefbfe42b // 11 메모리 주소 (10진) 140732920751147
//path = char * "/cat.c" 0x00007ffeefbfe424 // 4 메모리 주소 (10진) 140732920751140
//char 형이므로 결과값은 7바이트
//unsigned long t = (p - path) /sizeof(char);
//printf("%lu \n" , t); //7바이트
req->path = xmalloc(p-path);
//path = char * "/cat.c" 0x00007ffeefbfe424
strcpy(req->path, path);
//strncasecmp는 대소문자를 무시하고 2개의 문자열을 지정한 문자 개수까지만 비교합니다.
//결과 값이 같으면 0
//p = char * "HTTP/1.0\n" 0x00007ffeefbfe42b
if (strncasecmp(p, "HTTP/1.", strlen("HTTP/1.")) != 0) {
log_exit("parse error on request line (3): %s", buf);
}
//int stln = strlen("HTTP/1.");
//printf("HTTP/1. 문자 개수 : %d\n" , stln);
//7 만큼 주소 이동 1. 뒤에 있는 숫자를 얻으려는 목적 1.0 , 1.1 등등
p += strlen("HTTP/1.");
req->protocol_minor_version = atoi(p);
}
//헤더필드 읽어서 struct HTTPHeaderField* 반환
static struct HTTPHeaderField* read_header_field(FILE *in){
//printf("read_header_field 진입 - \n");
struct HTTPHeaderField *h;
char buf[LINE_BUF_SIZE];
char *p;
if (!fgets(buf, LINE_BUF_SIZE, in)) {
log_exit("failed to read request header field :%s" , strerror(errno));
}
printf("read_header_field - buf : %s \n" , buf);
if (buf[0] == '\n' || (strcmp(buf, "\r\n") == 0)) {
printf("read_header_field is null! \n");
return NULL;
}
p = strchr(buf, ':');
if (!p) {
log_exit("parse error on request header field : %s" , buf);
}
*p++ = '\0';
//HTTPHeaderField 구조체 크기만큼 메모리 할당
h = xmalloc(sizeof(struct HTTPHeaderField));
h->name = xmalloc(p - buf);
strcpy(h->name, buf);
//일치하지 않는 첫번째 인수 문자의 오프셋 찾기
p += strspn(p, " \t");
h->value = xmalloc(strlen(p) + 1);
strcpy(h->value, p);
return h;
}
//대문자로 변환 처리
static char* upcase(char *str){
//문자열 길이
int strLeng = (int)strlen(str);
//대문자로 변경된 값 담기위한 포인터
char *strResult;
strResult = malloc(strLeng);
if (!strResult) {
printf("failed to allocate memory\n");
}
//strResult : 0x7ffb82c00630
//printf("strResult : %p\n" , strResult);
for (int i = 0; i < strlen(str); i++) {
//문자 하나씩 대문자 변경 후 포인터에 할당
char result = (char)toupper((int)str[i]);
strResult[i] = result;
}
//strResult : ABCDEF
//printf("strResult : %s\n" , strResult);
str = strResult;
//str 값 : ABCDEF
//printf("str 값 : %s\n" , str);
//str 주소: 0x7ffb82c00630
//printf("str 주소 : %p\n" , str);
return strResult;
}
/* 응답 처리 함수*/
static void response_to(struct HTTPRequest *req, FILE *out , char *docroot){
if (strcmp(req->method, "GET") == 0) {
//http 요청 객체와 표준출력 , 루트경로 인자로 넘김
do_file_response(req, out, docroot);
}
else if(strcmp(req->method, "HEAD") == 0){
do_file_response(req, out, docroot);
}
else if(strcmp(req->method, "POST") == 0){
method_not_allowed(req, out);
}
else{
not_implemented(req, out);
}
}
//응답할 파일 읽고 처리 하는 함수
static void do_file_response(struct HTTPRequest *req , FILE *out , char *docroot){
struct myFileInfo *info;
info = get_fileinfo(docroot, req->path);
if (!info->ok) {
free_fileinfo(info);
not_found(req, out);
return;
}
//응답 성공
output_common_header_fields(req, out, "200 OK");
fprintf(out, "Content-Length: %ld\r\n", info->size);
fprintf(out, "Content-Type: %s\r\n" , guess_content_type(info));
fprintf(out, "\r\n");
//메소드가 HEAD 부분이 아니면
if (strcmp(req->method, "HEAD") !=0) {
int fd;
char buf[BLOCK_BUF_SIZE];
ssize_t n;
//파일열기
fd = open(info->path , O_RDONLY);
//열었을때 오류
if (fd < 0) {
log_exit("failed to open %s : %s" ,info->path , strerror(errno));
}
for (; ; ) {
//읽기
n = read(fd, buf , BLOCK_BUF_SIZE);
if (n < 0) {
log_exit("failed to read %s : %s" ,info->path , strerror(errno));
}
//다 읽었으면 for 문종료
if (n == 0) {
break;
}
if (fwrite(buf, 1, n, out) < n) {
log_exit("failed to write to socket");;
}
}// for - end
close(fd);
}
//출력 버퍼 비우기
fflush(out);
//파일 정보 구조체 메모리 해제
free_fileinfo(info);
}
//여기서 사용하는 구조체와 그 구조체 안에서 사용하는 char* 등의 영역은 모두 malloc()
//으로 할당된다. 따라서 사용이 끝나면 free 해야 한다.
static void free_request(struct HTTPRequest *req){
struct HTTPHeaderField *h , *head;
head = req->header;
while (head) {
h = head;
//h를 free 하기전에 다음에 연결된 HTTP 헤더인 h->next를 취득해 두어야 함!
head = head->next;
free(h->name);
free(h->value);
free(h);
}
free(req->method);
free(req->path);
free(req->body);
free(req);
}
//콘텐츠 길이 구하기
static long content_length(struct HTTPRequest *req){
char *val;
long len;
val = lookup_header_field_value(req, "Content-Length");
if (!val) {
return 0;
}
//int 형으로 변환
len = atol(val);
//길이가 0 이하면 오류..
if (len < 0) {
log_exit("negative Content-Length value");
}
return len;
}
//헤더의 콘텐츠 길이 구하기
static char* lookup_header_field_value(struct HTTPRequest *req , char *name){
struct HTTPHeaderField *h;
for (h = req->header; h; h = h->next) {
if (strcasecmp(h->name, name) == 0) {
return h->value;
}
}
return NULL;
}
//메모리 관리
//메모리를 사용할때 정적 버퍼는 되도록 사용하지 않고, malloc 계의 api 사용.
//메모리 할당 + 메모리 할당 실패 처리
static void* xmalloc(size_t sz){
void *p;
//printf("xmalloc 함수 진입 - 인자값(size) : %lu\n" , sz);
p = malloc(sz);
if (!p) {
log_exit("failed to allocate memory");
}
return p;
}
/* 에러 처리 함수 */
static void
method_not_allowed(struct HTTPRequest *req, FILE *out)
{
output_common_header_fields(req, out, "405 Method Not Allowed");
fprintf(out, "Content-Type: text/html\r\n");
fprintf(out, "\r\n");
fprintf(out, "<html>\r\n");
fprintf(out, "<header>\r\n");
fprintf(out, "<title>405 Method Not Allowed</title>\r\n");
fprintf(out, "<header>\r\n");
fprintf(out, "<body>\r\n");
fprintf(out, "<p>The request method %s is not allowed</p>\r\n", req->method);
fprintf(out, "</body>\r\n");
fprintf(out, "</html>\r\n");
fflush(out);
}
static void
not_implemented(struct HTTPRequest *req, FILE *out)
{
output_common_header_fields(req, out, "501 Not Implemented");
fprintf(out, "Content-Type: text/html\r\n");
fprintf(out, "\r\n");
fprintf(out, "<html>\r\n");
fprintf(out, "<header>\r\n");
fprintf(out, "<title>501 Not Implemented</title>\r\n");
fprintf(out, "<header>\r\n");
fprintf(out, "<body>\r\n");
fprintf(out, "<p>The request method %s is not implemented</p>\r\n", req->method);
fprintf(out, "</body>\r\n");
fprintf(out, "</html>\r\n");
fflush(out);
}
static void
not_found(struct HTTPRequest *req, FILE *out)
{
output_common_header_fields(req, out, "404 Not Found");
fprintf(out, "Content-Type: text/html\r\n");
fprintf(out, "\r\n");
if (strcmp(req->method, "HEAD") != 0) {
fprintf(out, "<html>\r\n");
fprintf(out, "<header><title>Not Found</title><header>\r\n");
fprintf(out, "<body><p>File not found</p></body>\r\n");
fprintf(out, "</html>\r\n");
}
fflush(out);
}
#define TIME_BUF_SIZE 64
//공통 헤더 필드
static void output_common_header_fields(struct HTTPRequest *req, FILE *out, char *status){
//시간 처리 객체
time_t t;
struct tm *tm;
char buf[TIME_BUF_SIZE];
t = time(NULL);
tm = gmtime(&t);
if (!tm) {
log_exit("gmtime() failed : %s " , strerror(errno));
}
strftime(buf, TIME_BUF_SIZE, "%a , %d %b %Y %H:%M:%S GMT", tm);
//응답 상태
fprintf(out, "HTTP/1.%d %s\r\n" , HTTP_MINOR_VERSION , status);
fprintf(out, "Date: %s\r\n", buf);
fprintf(out, "Server : %s/%s\r\n" , SERVER_NAME , SERVER_VERSION);
fprintf(out, "Connection: close\r\n");
}
//FileInfo 정보 리턴
static struct myFileInfo* get_fileinfo(char *docroot , char *path){
struct myFileInfo *info;
//stat, lstat, fstat 함수들은 모두 파일 정보를 읽어오는 함수이다.
struct stat st;
info = xmalloc(sizeof(struct myFileInfo));
info->path = build_fspath(docroot, path); //루트경로 + 파일경로
info->ok = 0;
/*
lstat - 반환값
-각 함수들의 호출 성공시 0을 반환하며 두번째 인자인 stat 구조체에 파일 정보들로 채워진다.
실패 혹은 에러시 -1을 리턴하고 에러시에 errno 변수에 에러 상태가 set된다.
*/
if (lstat(info->path, &st) < 0) {
return info;
}
//S_ISREG – 정규 파일인지 판별
if (!S_ISREG(st.st_mode)) {
return info;
}
info->ok = 1;
info->size = st.st_size;
return info;
}
//루트 경로 + 파일 경로 반환
static char* build_fspath(char *docroot, char *urlpath){
char *path;
path = xmalloc(strlen(docroot) + 1 + strlen(path) + 1);
sprintf(path, "%s/%s" , docroot , urlpath);
return path;
}
//파일 정보 구조체 해제
static void free_fileinfo(struct myFileInfo *info){
free(info->path);
free(info);
}
//콘텐트 타입 반환
static char* guess_content_type(struct myFileInfo *info){
return "text/html";
}
//서버에서 에러가 발생했을때 처리
//printf 와 같은 형식의 인자를 받아 표준에러에 출력하고 exit 한다.
// ...은 가변인자를 뜻한다. 즉, printf만큼 원하는 만큼 인자를 전달할 수 있다.
//stdarg include 필요.
static void log_exit(const char *fmt , ...){
//가변인자 처리를 위한 va_list 타입 변수
//가변인자가 모두 들어감.
va_list ap;
//두번째 인자에는 가변 인자가 적용된 인자의 변수를 쓴다.
//va_start : 매크로이고, 인자의 주소를 바탕으로 가변인자가 위치한 주소를 계산하는 구조
va_start(ap, fmt);
//va_list 를 직접 전달할 수 있는 표준 함수
//에러 구문 출력
if (debug_mode) {
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
}
else {
vsyslog(LOG_ERR, fmt, ap);
}
va_end(ap);
exit(1);
}
view raw http3.c hosted with ❤ by GitHub

 

예제소스 - index.html

<meta charset="UTF-8">
<html>
<head><title>Network</title></head>
<body>
<font size=+5>
this is a test page !!! hi!
</font>
<br>
<!-- <img src="mypic.jpg" alt="Trulli" width="300" height="300"> -->
</body>
</html>
view raw index.html hosted with ❤ by GitHub

 

 

결과

데몬 프로세스로 작동시키기
터미널에서 ps -ef 명령어로 보면 위에서 실행시킨 프로세스가 데몬프로세스로 돌고있는것을 확인할 수있다. 웹브라우저를 띄워서 포트 번호와 index.html 파일 명을 입력하면 index.html 파일내용이 브라우저에 출력된 것을 볼 수 있다. 또한 요청 메시지와 응답메시지도 잘 출력되었음을 알 수 있다.

 

예제파일

0827 2.zip
0.02MB
http - 2.pdf.zip
7.65MB