HTTP에 관한 이론과 지식들을 앞서 공부해보았고,
실제 실습과 구현을 통해 더 체득해보자!
실습에 진행하기 앞서, 소켓 통신에 관련해서는 네트워크 전공에서 공부했었는데
깃허브에 올라와있는 코드를 따라쳐보고 직접 구글링해가면서
각 헤더파일에 대한 역할과 함수의 역할, 매개변수와 인자 등을
공부하고 이해하고 복습하는 방향이 제일 빠르게 습득할 수 있을 것 같아서
다음 주소를 참고하였다.
HTTP Server in C : https://gist.github.com/laobubu/d6d0e9beb934b60b2e552c2d03e1409e
해당 깃허브에 들어가게 되면 4가지의 파일 형태를 볼 수 있고,
HTTP 서버 구현을 위한 파일 구성도는 다음과 같다.
✔️ HTTP 서버 구현을 위한 파일 구성도
만든게 조금 초라하긴 한데···
코드를 작성한 순서는 구성도와 같이 작성을 해주었다.
그 이유는 httpd.c와 main.c 모두 httpd.h라는 같은 헤더파일을 포함하고 있어서
헤더파일부터 작성해주었더니 c 파일을 작성해줄때 조금 더 이해하기 수월했다.
해당 구성도는 마지막 C 코드의 컴파일과 산출물을 쉽게 만들어주는
Makefile을 만드는 데에도 쉽게 이해하도록 도움을 주니 기억해두고 가면 좋다.
다음은 본격적으로 코드에 대한 이야기를 해보려고한다.
코드는 솔직하게 누군가가 만들어놓은 코드를 그냥 컨트롤 CV를 하면 안된다..
이 부분은 코딩 공부할때도 느낀 것이지만 절대 내 코드가 될 수 없다는 것을 안다.
그래서 이미 구현되어있는 코드를 가져올 때도 고민했지만,
해당 코드를 직접 쳐보고 구글링하면서 코드에 주석을 달고 충분히 고민해보며
공부해보는 시간을 가졌다. 맨 땅에 해딩하는 것도 많은 도움이 되지만,
좋은 코드와 좋은 로직 구성을 보고 검색 실력을 늘리것도 방법이라 생각하며 진행했다.
✔️ httpd.h 헤더 파일 만들기
#ifndef _HTTPD_H___
#define _HTTPD_H___
#include <string.h>
#include <stdio.h>
// 위에서 나오는 #ifndef 부터 #endif 까지는
// if not defined의 약자로 _HTTPD_H__ 를 정의하지 않았다면
// 밑에 정의한 내용들을 include 영역에 포함한다는 얘기이다.
// 서버 제어 함수
// 반환 값이 없는 함수를 만들기 때문에
// void로 설정하는 것 같음.
void server_forever(const char *PORT);
// 클라이언트 요청
char *method, // GET 메소드 or POST 메소드 정의
*uri, // '/index.html'과 같은 uri 정의
*qs, // 'id=guest&pw=guest'와 같은 uri에 붙는 값 정의 (querystring 약자로 추측)
*prot; // 'HTTP/1.1'과 같은 프로토콜 정의 (protocol 약자)
char *payload; // POST를 위한 payload라고 명시되어 있음
int payload_size; // payload를 위한 size 정의
// 앞서 배운 HTTP Request Header를 받기위한 name 매개변수 정의
char *request_header(const char* name);
// 구현해야 하는 함수, 기능 > 라우트 정의
void route();
// 정의한 라우트 함수를 위한 매개변수 정의하기
// 다음 조건문에서 수행하는 것은 URI와 uri 문자열을 비교한 값이 같고(and)
// METHOD와 method 문자열을 비교한 값이 같으면
// 정의한 라우트에 GET과 POST 메소드를, URI는 URI로 각각 처리한다.
// 그게 아니면 500 에러를 띄운다.
// cf. 참고로 500번대 에러란, Server Error를 의미한다.
#define ROUTE_START() if (0) {
#define ROUTE(METHOD,URI) } else if (strcmp(URI,uri)==0&&strcmp(METHOD,method)==0) {
#define ROUTE_GET(URI) ROUTE("GET", URI)
#define ROUTE_POST(URI) ROUTE("POST", URI)
#define ROUTE_END() } else printf(\
"HTTP/1.1 500 Not Handled\r\n\r\n" \
"The server has no handler to the request.\r\n" \
);
#endif
원래는 코드를 쭉 작성하고, 블로그에 해당 내용이 무엇인지 설명하려했으나
코딩을 하면서 '아, 이 부분이 뭐였지?' 라는 고민을 해결하기 위해
코드 양이 많아졌지만 모두 주석처리해가면서 공부하였다.
주석도 주석이지만, 코드를 작성하고 복습한다는 마음으로
다시 한번 코드를 살펴보려고 한다.
우선 맨 처음에 나오는 #ifndef부터 #endif 까지 본 헤더 파일은
전처리문으로 작업한다 혹은 해당 내용들을 정의한다는 것인데,
컴파일 이전에 미리 처리되게 하기 위해서 그리고
중복 정의를 피하기 위해서 사용한다고 한다.
즉, _HTTPD_H___를 정의하지 않았다면, #endif가 나오기 전까지
#define부터 밑에 나오는 내용들을 포함하여 정의하는 것이다.
그 다음부터는 void 형으로 정의해준 server_forever 함수와
method, uri, qs, prot, payload, payload_size 변수 정의를 해준 부분이 나온다.
그리고나서 요청할 HTTP Request header에 대해
매개변수 name을 받는 request_header를 선언하고
라우트를 정의하기 위한 void형 route 함수를 선언하게 된다.
(ps. 깃헙에는 serve_forever라고 되어있는데, 이 부분은 나중에 컴파일 에러를
1시간동안 삽질하게 된 원인이 되었다고 한다···)
자세하게 한번 살펴보자.
void server_forever(const char *PORT);
char *method,
*uri,
*qs,
*prot;
char *payload;
int payload_size;
char *request_header(const char* name);
void route();
httpd.h 헤더 파일을 먼저 작성해줘야겠다고 생각하는 부분이 여기에서 나온다.
다음 나올 c 파일들에서 해당 선언된 변수를 받기 때문에,
'이 변수는 위에서 선언한적이 없는데..?'라고 생각이 들면 이 내용 때문일 것이다.
1) HTTP 메소드 중 GET과 POST 메소드를 위한 method 선언
2) /index.html과 같은 문자열이 오는 uri 선언
3) uri 뒤에 붙는 값, 즉 query string에 대한 qs 선언
4) protocol을 위한 prot 선언
5) 전송되는 데이터를 의미하는 payload 선언
6) 전송되는 데이터의 크기를 위한 payload_size 선언
7) name을 매개변수로 받는 요청을 위한 헤더 request_header 선언
8) 이후 사용자가 정의할 수 있는 라우팅 함수 route 선언
다음과 같이 진행하고 나면,
이후에 사용자가 정의해줄 라우팅 함수에 사용할 선언 값들이 나온다.
#define ROUTE_START() if (0) {
#define ROUTE(METHOD,URI) } else if (strcmp(URI,uri)==0&&strcmp(METHOD,method)==0) {
#define ROUTE_GET(URI) ROUTE("GET", URI)
#define ROUTE_POST(URI) ROUTE("POST", URI)
#define ROUTE_END() } else printf(\
"HTTP/1.1 500 Not Handled\r\n\r\n" \
"The server has no handler to the request.\r\n" \
);
쓰임새는 다음과 같다.
① ROUTE_START() : 라우트의 시작지점 정의 (호출지점)
② ROUTE(METHOD, URI) : 오른쪽에서 사용할 조건문을 위한 정의
③ ROUTE_GET(URI) : URI를 지정해 GET 메소드로 넘길 라우트 정의
④ ROUTE_POST(URI) : URI를 지정해 POST 메소드로 넘길 라우트 정의
⑤ ROUTE_END() : 라우트의 끝지점 정의 (해제지점)
조건문에 나와있는 것처럼,
조건이 True 이면서 URI 값과 uri 값을 strcmp로 문자열 비교한 값이 같으면 0을 출력하는데,
METHOD 값과 method 값이 같을 때까지를 논리 연산자 &&를 활용해서 모두 True 이면,
2번에서 정의된 메소드와 URI를 라우트에 담아서 넘겨주게 된다.
모두 아니라면 그것은 서버 에러와 같은 것이 되기 때문에
printf 출력문을 통해 500 에러라는 출력 문자열을 반환해준다.
파일 끝에 값을 이어쓰기 위해서 \를 작성해준 것을 확인할 수 있다.
참고로 뒤에서도 설명하겠지만, 마지막 500 에러 출력에서 사용된 제어문자인 \r과 \n은
캐리지 리턴과 라인 피드라고 불리는 것으로 첫 행으로 이동하는 것, 개행 문자로 불리우는데
HTTP 통신에서 프록시를 잡아보거나 통신하는 첫 http status 상태를 보게 되면,
'HTTP/버전 통신Status \r\n\r\n'과 같이 필히 해당 방식으로 전달되는 것을
확인해줄 수 있기 때문에 뒤에도 나올 코드에 들어가는
캐리지 리턴과 라인 피드의 반복을 기억해두면 좋다.
해당 코드는 Vm 가상환경의 우분투 vim을 통해 작성하였으며,
이어서 HTTP 서버 구현의 핵심 코드인 httpd.c에 대해 공부해보자.
화이팅 💪
'Study > C언어' 카테고리의 다른 글
[C언어] HTTP 서버 구현.c (3) (0) | 2022.10.08 |
---|---|
[C언어] HTTP 서버 구현.c (2) (0) | 2022.10.07 |
[C언어] 코딩도장 공부 (Unit 61~80) (0) | 2022.09.24 |
[C언어] 코딩도장 공부 (Unit 41~60) (0) | 2022.09.23 |
[C언어] 코딩도장 공부 (Unit 21~40) (0) | 2022.09.22 |