웹 클라이언트와 서버는
HTTP, Hypertext Transfer Protocol
라는 텍스트 기반 응용 수준 프로토콜로 상호 연동한다.
웹 클라이언트(웹브라우저)가
연결을 잇고
컨텐츠를 요청한다.
서버는 컨텐츠로 응답하고
연결을 닫는다.
브라우저는 컨텐츠를 읽고
이것을 스크린에 보여준다.
FTP 등과 차이점은,
웹 컨텐츠는 HTML, Hypertext Markup Language라는
언어로 작성될 수 있다.
HTML 프로그램(페이지)은 명령들(태그)를 포함하고 있어
브라우저에게 여러가지 텍스트와 그래픽 객체를 어떻게 표시할지 알려준다.
ex)
<b> Make me bold! </b>
는 양쪽 태그 사이 텍스트를 굵은 타입으로 인쇄하라고 알려준다.
그러나 그보다 HTML이 강력한 이유는,
포인터(하이퍼링크)를 포함할 수 있다는 점이다.
지시한 줄을 클릭하면,
서버 내 html 파일을 가져와서 화면에 표시한다.
웹의 클라이언트와 서버에게
컨텐츠는 연관된 MIME 타입을 갖는 바이트 배열이다.
웹 서버는 두 가지 서로 다른 방법으로
클라이언트에게 컨텐츠를 제공한다.
디스크 파일을 가져와서 그 내용을 클라이언트에게 보낸다.
이 디스크 파일을 정적 컨텐츠라고 하며,
파일을 클라이언드에게 돌려주는 작업을 정적 컨텐츠를 처리한다라고 한다.
실행 파일을 돌리고, 그 출력을 클라이언트에게 보낸다.
실행 파일이 런타임에 만든 출력을 동적 컨텐츠라고 하며,
프로그램을 실행하고 그 결과를 클라이언트에게 보내주는 과정을
동적 컨텐츠를 처리한다라고 한다.
웹 서버가 리턴하는 모든 내용은
서버가 관리하는 파일에 연관된다.
이 파일 각각은 URL, Universal Resource Locator라고 하는
고유의 이름을 가진다.
(그야 모두에게 통용되는 리소스 위치라고 하면
전체 지도에서 부합하는 주소 같은 거니까 고유하겠지...)
예를 들어,
http://www.google.com:80/index.html
의 경우
www.google.com에 있는 index.html 파일을 가져온다.
(HTTP는 기본 80이니 80이다.)
실행파일에서의 URL은
프로그램 인자를 포함할 수 있다.
예를 들어,
http://blufish.ice.cs.cmu.edu:8000/cgi-bin/adder?15000&213
은, cgi-bin/adder이라는 실행파일을 식별하고,
이 파일은 인자 15000와 213와 함께 호출된다.
이러한 접미어 해석에는
참고해야할 사항이 있다.
HTTP가 인터넷 연결 위로 전송된 텍스트 라인에 기초하고 있어,
리눅스 TELNET 프로그램으로 인터넷 상
모든 웹서버와 트랜잰션을 수행할 수 있다...
(transaction은 거래 라는 뜻을 갖고 있다.)
telnet은
요청 라인
GET / HTTP/ 1.1
(method / URI / version)
과
요청 헤더
Host : www.aol.com
그리고
헤더 리스트를 종료하는 빈 텍스트줄이 따라온다.
...
HTTP는 서로 다른 method를 지원한다.
GET, POST, OPTIONS, HEAD, PUT, DELETE, TRACE
등이 있지만, GET만 살펴보자.
GET은
서버에게 URI, Uniform Resource Identifier에 의해
식별되는 내용을 리턴할 것을 지시한다.
URI는 파일 이름과 옵션인 인자들을 포함하는
URL의 접미어다.
(라고는 하는데
챗 지피티는 URI가 특정 리소스를 나타내는 문자열로
URL, URN 두 종류가 있다네.
대충 둘 중 상관없으니 GET은 리소스 위치를 가져온다는것같군.)
요청라인의 version 필드는 요청이 준수하는 HTTP 버전이다.
현재 쓰이고 있는건 1.1로, 1.0보다 간단한 버전으로서
캐싱과 보안, 클라이언트와 서버가 동일하게 지속적으로 연결 위에
다중 트랜잭션을 수행할수 있도록 하는 메커지늠을 가진
고급 기능을 지원하는 추가적 헤더를 정의하고 있다.
두 버전이 호환성이 있긴 한데,
그건 1.0에서 알려지지 않은 1.1헤더는 그냥 무시해서 그렇다.
서버에
브라우저 이름이나, 브라우저가 이해하는 MIMT 타입 같은
추가적 정보를 제공한다.
요청 헤더는 다음과 같은 형식이다.
header-name : header-data
예시로 나온 요청은 HTTP/1.1 에서 요구되는 부분이다.
Host 헤더는 프록시 캐시 에 의해 사용되며,
때로 브라우저와 요청된 파일을 관리하는 본래 서버 사이 중간자 역할을 한다.
하나의 클라이언트와
하나의 본래 서버 사이에,
프록시 체인내 다중 프록시가 존재할 수 있다.
원점 서버의 도메인 이름을 식별하는 Host 헤더 내 데이터는
프록시 체인 중간에 있는 프록시가 요청한 컨텐츠의 지역적으로
캐시된 사본을 가질 수 있는지 결정할 수 있도록 한다.
말을 어렵게했는데,
어차피 클라이언트와 본래 서버 사이에,
중개하는 프록시 서버가 있기때문에,
요청 헤더를 봐서
앗! 나 이거 캐시 사본으로 있어.
인지 확인해서 거리를 줄일 수 있다는 것이다.
HTTP 응답은 HTTP 요청과 비슷하다.
응답은
응답 라인,
요청 헤더들
헤더를 종료하는 빈줄
그리고 응답 본체가 따라온다.
version , status-code, status-message
형태를 하는데,
버전은 응답이 준수해야할 HTTP 버전,
상태 코드는 3비트 양수로 요청의 특성,
상태 에시지는 에러코드를 문장으로 명시한다.
응답 헤더는 추가적인 정보를 제공하는데,
MIME 타입을 알려주는 Content-Type과
크기를 바이트로 나타낸 Content-Length를 주의깊게 보자.
서버가 동적 컨텐츠를
클라이언트에 전한다고 했을떄...
몇 가지 의문점이 있다.
이러한 질문은 CGI, Common Gateway Interface라고 부르는 표준으로
설명할 수 있다.
GET에서 인자들은
URI에서 전달된다.
앞에서 말했듯,
'?'라는 표식이 파일 이름과 인자를 구분하고,
'&'로 인자들을 구분한다.
빈칸은 인자들 사이 허용되지 않고,
%20 등의 스트링으로 표시해야한다.
서버가
GET /cgi-bin/adder?15000&213 HTTP/1.1
을 요청 받으면
fork 를 호출해서
자식 프로세스를 생성,
execve를 호출해서
/cgi-bin/adder 프로그램을
자식의 컨텍스트에서 실행한다.
(단순히 말하면 자식 프로세스에서 실행한다는 거겠지.)
adder 같은 프로그램은 종종 CGI 프로그램이라 부르는데,
그 이유는 CGI 표준 규칙을 준수하기 때문이다.
execve 호출하기 전,
자식 프로세스가 CGI 환경변수 QUERY_STRING으로
15000&213으로 설정하여,
adder 프로그램이 런타임때 getenv 함수로 이 값을 참조할 수 있다!
(뭐 일단 호출 전에
미리 전역적으로 선언하는 거같은데...)
(자식 프로세스 내에서 실행되니까
환경 변수로 저장해놓고 꺼내오는것 같다.)
CGI는 CGI 프로그램실행시,
설정 되어있어야하는 환경변수의 개수를 정의한다.
(아마 이런 식으로 정보를 넘겨주는듯.)
QUERY_STRING을 쓰는 것 같다.
자식 프로세스가
CGI 프로그램을 로드하고 실행하기 전,
리눅스 dup2 함수를 사용하여
표준 출력을 클라이언트와 연계된 연결 식별자로 재지정한다.
(본래의 출력을
연결 식별자로 기본 설정하는 것 같다.)
그래서 CGI 프로그램의 표준 출력은 모두
클라이언트로 직접 가게 된다!
부모는 자식이 생성한
컨텐츠의 종류와 크기를 모르기때문에
자식은 Content-type, Content-length 응답 헤더,
헤더를 종료하는 빈줄까지 꼭 생성해야한다.
두 인자를 더하고
클라이언트에게 결과와 HTML 파일을 리턴하는
간단한 프로그램을 보자.
#include "csapp.h"
int main(void) {
char *buf, *p;
char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
int n1= 0, n2=0;
if((buf = getenv("QUERY_STRING")) != NULL) {
p = strchr(buf, '&');
*p = '\0';
strcpy(arg1, buf);
strcpy(arg2, p+1);
n1 = atoi(arg1);
n2 = atoi(arg2);
}
// 응답 본체
sprintf(content,"QUERY_STRING=%s", buf);
sprintf(content, "Welcome to add.com: ");
sprintf(content, "%sTHE Internet addition portal. \r\n<p>", content);
sprintf(content,"%sThe answer is : %d + %d = %d\r\n<p>", content, n1, n2, n1 + n2);
sprintf(content, "%sThanks for visiting!\r\n", content);
// HTTP응답 생성.
printf("Connection: close\r\n");
printf("Contente-length: %d\r\n", (int)strlen(content));
printf("Content-type: text/html\r\n\r\n");
printf("%s", content);
fflush(stdout); // 표준 출력 버퍼 비우기. 강제로 출력시킴.
exit(0);
}
두번째 if문은 대략
15000&213
ㅡㅡㅡ^ㅡㅡㅡ
를 가리키고
& => \n으로 변환
15000\n213
이 되면
n1 < = 뒤에 잘린 것 기준까지 앞 15000 저장
n2 <= /n 뒤에 첫번째 글자부터 뒷부분 저장.
근데 저장할때 atoi로 문자열 -> 정수 과정을 거침.
...
그리고 먼저 sprintf로
앞으로 출력할 문자열을 동적으로 만든다음
printf로 쭉 출력하면서
마지막으로 여태까지 만들었던 content를 출력해서
응답헤더
응답 본문
형식을 유지한다.