
예제 소스 - http.c
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
/* | |
- 소켓 접속 | |
- 데몬화 | |
- 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); | |
} | |
예제소스 - index.html
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
<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> |
결과


예제파일
0827 2.zip
0.02MB
http - 2.pdf.zip
7.65MB
'컴퓨터 기초 > TCP&IP' 카테고리의 다른 글
19.C언어 HTTP 서버 구축하기 (표준입출력 이용) (0) | 2020.08.27 |
---|---|
18.http 서버 (0) | 2020.08.24 |
17.멀티 스레드를 이용한 채팅 서버 (0) | 2020.08.24 |
16.입출력 스트림의 분리에 대한 나머지 이야기 (0) | 2020.08.19 |
15.소켓과 표준 입출력 (0) | 2020.08.19 |