CGI Tips
최윤수
대전 광역시 유성구 유성우체국 사서함 122호
한국과학기술연구원 연구개발정보센터
E-mail : armian@www.kordic.re.kr
URL :
http://stissbs.kordic.re.kr/~armian/
- Abstract:
- Internet상에서 WWW의 이용자가 급증함에 따라, WWW를 이용한 서비스는 이제 일반적
인 사양이 되었다.
- WWW를 이용한 서비스는 정적인 문서들을 모아, 하이퍼링크를 통해 보여주는 정보제공
방식과, 사용자의 요구에 따라 동적인 문서를 생성하여 제공하는 방식이 있다.
- 이러한 동적인 문서는 CGI를 통해 구현되는 것이 일반적인 방법인데, CGI프로그래밍의 역
사는 그리 오래되지 않아서, 아직도 많은 문제점을 담고 있다.
- 본 고에서는 그동안 CGI를 이용하여 사용자 인터페이스를 개발하면서 느꼈던 어려웠던 점
과 해결방안들을 제시하여, CGI프로그래밍을 시작하고자 하는 이들에게 미약하나마 도움을 주고
자 한다.
-
- Keywords:
- CGI,Form,Debug
1. CGI & Debugging
CGI 프로그래밍을 작성하다보면 아래와 같은 에러메시지를 자주 접하게 된다.
일반적으로 UNIX에서 C 프로그래밍을 하다 보면 겪게 되는 "Segmentation fault"에러가 이러한
식으로 표현되기 때문이다.
500 Server Error
The server encountered an internal error or misconfiguration and was unable
to complete your request.
Please contact the server administrator,
armian@stissbs.kordic.re.kr and inform them of the time the error occurred,
and anything you might have done that may have caused the error.
Error: httpd: malformed header from script
<그림 1> CGI & Error message
CGI 프로그래밍시 가장 어려운 부분은 아마도 위와 같은 에러가 났을 때, 디버깅을 어떻게
해야 하는 가 일 것이다. UNIX상에서의 디버깅은 dbx같은 디버깅 툴을 사용하여 쉽게 해결할 수
있었지만, CGI에 대한 디버깅은 전혀 다른 상황이기 때문이다.
결국 CGI 디버깅을 손쉽게 하기 위해서는, 일반적인 C 프로그램과 동일하게
UNIX상에서 실행될 수 있도록 변환해야 하는 작업이 필요하다.
사실 CGI 프로그램이 일반적인 C프로그램과 다른 점은 단 하나, 입력으로 받아 들이는 파라
미터들에 처리 방식 뿐이다. 즉, 처음부터 입력을 받을 수 있는 창구를 두 가지형태로 작성을 한
다면, 디버깅 때문에 고생을 할 필요가 없다는 결론이 나온다.
아래에 이러한 형태의 예제 프로그램을 보면 쉽게 이해할 수 있을 것이다.
1.1 디버깅을 위한 예제 프로그램
#include
main(int argc, char *argv[])
{
getentry entries[100];
register int x, m = 0;
char *cl;
char *ptr;
#ifdef DEBUG /* 디버깅을 위한 루틴 */
if (argc < 2)
{
printf("\n %s name1=val1 [name2=val2]......\n");
exit(0);
}
for(x=1; x < argc; x++)
{
ptr = (char *) strchr(argv[x],'=');
if (ptr == NULL)
{
printf("\n Error in input...\n");
exit(0);
}
*ptr = '\0';
strcpy(entries[x-1].name,argv[x]);
strcpy(entries[x-1].val,ptr+1);
}
m = argc-2;
#else /* CGI Input을 처리하기 위한 루틴 */
printf("Content-type: text/html%c%c", 10, 10);
if (strcmp(getenv("REQUEST_METHOD"), "GET"))
{
exit(1);
}
cl = getenv("QUERY_STRING");
if (cl == NULL)
{
printf("No query information to decode.\n");
exit(1);
}
for (x = 0; cl[0] != '\0'; x++)
{
m = x;
getword(entries[x].val, cl, '&');
plustospace(entries[x].val);
unescape_url(entries[x].val);
getword(entries[x].name, entries[x].val, '=');
}
#endif
printf("\nQuery Results
");
printf("\nYou submitted the following name/value pairs:%c", 10);
printf("\n
%c", 10);
for (x = 0; x <= m; x++)
printf("\n-
%s = %s%c", entries[x].name,
entries[x].val, 10);
printf("\n
%c", 10);
}
1.2 컴파일 방법
- 디버깅을 위한 컴파일
cc -o query query.c -DDEBUG
- CGI 프로그램으로 컴파일
cc -o query.cgi query.c
1.3 실행결과
[stissbs:WWW4 10 ] query name=armian tel=042-828-5045
Query Results
You submitted the following name/value pairs:
-
name = armian
-
tel = 042-828-5045
2. 환경변수를 이용한 처리
2.1 GET,POST METHOD 처리
CGI에서는 두 가지 형태의 METHOD를 사용하여 브라우저와 CGI사이에서 사용되는 파라미터를
전달한다. 바로 GET Method와 POST Method인데, CGI 프로그래밍시에 종종 Method를 바꾸어
야 할 필요가 발생하기도 하고, 두 가지 Method를 동시에 지원할 필요도 있다.
이러한 상황을 해결하기 위해, 두 가지 Method를 모두 다 사용할 수 있도록 CGI 프로그램의 파
라미터 처리 부분을 변경한 프로그램은 아래와 같다.
printf("Content-type: text/html%c%c", 10, 10);
if (!strcmp(getenv("REQUEST_METHOD"), "GET"))
{
......
GET METHOD 처리 루틴
......
}
else if (!strcmp(getenv("REQUEST_METHOD"), "POST"))
{
......
POST METHOD 처리 루틴
......
}
else
{
......
ERROR 처리 루틴
......
}
2.2 HTTP_USER_AGENT를 이용한 처리
HTTP_USER_AGENT를 이용하여, 상대방의 브라우저타입을 알 수 있는데, 이를 이용하여
각각의 브라우저에 적당한 형식의 출력을 행할 수 있다.
예를들어, lynx같은 라인모드 브라우저는 텍스트 밖에 출력이 안되므로, 다른 브라우저와는
다르게 출력을 해야 한다.
<예제 프로그램>
agent = (char *)getenv("HTTP_USER_AGENT");
if (!strncmp(agent,"Lynx",4))
{
......
텍스트 데이터를 출력
......
}
else
{
......
멀티미디어 데이터를 출력
......
}
2.3 REMOTE_ADDR을 이용한 처리
REMOTE_ADDR를 사용하여 자신의 홈페이지로 들어온 상대방의 IP 어드레스를 얻어낼 수
있다.
특정한 호스트에 대해 특별한 행동을 해야 할 때 사용하면 된다.
예를들어, UNIX시스템에서 관리자(root)로의 로그인은 그 호스트에서만 가능하게 할 수 있
는 것처럼, Web BBS나 정보검색시스템의 관라자는 특정한 호스트 또는도메인에서만 로그인하게
할 수 있다.
3. CGI 출력
CGI에서 브라우저로의 출력은 귀챦은 작업 중의 하나이다. 잔손질도 많이 들어가고, 약간의
수정을 하려면, 새로이 컴파일과 링크 작업을 거쳐야 하기 때문에, 시간도 많이 뺏기는 작업이다.
다음의 두 예제를 비교해 보면, 어떠한 방법이 더 효율적인가를 바로 알 수 있다. 이 예제들
은 브라우저를 통해 세 개의 입력(name,title,color)을 받아 들여, 출력하는 간단한 CGI 프로그램이
다.
첫번째 예제는 일반적인 출력방법으로, 프로그램내에서 printf()함수를 사용하여 모든 출력을
행하는 방법이다.
두번째 예제는 외부 화일로 출력하고자 하는 내용을 HTML문서로 작성해 놓고, 사용자의
입력에 따라 출력내용을 변형시켜 출력하는 방법이다.
<예제 1> 일반적인 출력방법
#include
main(int argc, char *argv[])
{
getentry entries[100];
register int x, m = 0;
char *cl;
char *ptr;
char name[100],title[100],color[100];
printf("Content-type: text/html%c%c", 10, 10);
if (strcmp(getenv("REQUEST_METHOD"), "GET"))
{
exit(1);
}
cl = getenv("QUERY_STRING");
for (x = 0; cl[0] != '\0'; x++)
{
m = x;
getword(entries[x].val, cl, '&');
plustospace(entries[x].val);
unescape_url(entries[x].val);
getword(entries[x].name, entries[x].val, '=');
}
for (x = 0; x <= m; x++)
{
if (!strcmp(entries[x].name,"name"))
{
strcpy(name,entries[x].val);
continue;
}
if (!strcmp(entries[x].name,"title"))
{
strcpy(title,entries[x].val);
continue;
}
if (!strcmp(entries[x].name,"color"))
{
strcpy(color,entries[x].val);
continue;
}
}
printf("");
printf("\n");
printf("\n %s ",title);
printf("\n");
printf("\n",color);
printf("\n%s
",title);
printf("\n
");
printf("\n");
printf("\n\tYour name : %s",name);
printf("\n\tYour Title : %s",title);
printf("\n\tYour Color : %s",color);
printf("\n
");
printf("\n
");
printf("\n");
printf("\n");
}
<예제 2-1> 화일을 이용한 출력방법(HTML Prototype)
:TITLE:
:TITLE:
Your name : :NAME:
Your Title : :TITLE:
Your Color : :INPUTCOLOR:
<예제 2-2> 화일을 이용한 출력방법(CGI 프로그램)
#include
#define MAXBUFSIZE 1000
#define INPUTFILE "title.html"
main(int argc, char *argv[])
{
.....
char buffer[MAXBUFSIZE];
char newbuf[MAXBUFSIZE];
FILE *fp;
....
fp = fopen(INPUTFILE,"r");
while(1)
{
fgets(buffer,MAXBUFSIZE,fp);
if(feof(fp)) break;
ptr = (char *)strstr(buffer,":TITLE:");
if (ptr)
{
*ptr = '\0';
sprintf(newbuf,"%s%s%s",buffer,title,ptr+7);
puts(newbuf);
continue;
}
ptr = (char *)strstr(buffer,":NAME:");
if (ptr)
{
*ptr = '\0';
sprintf(newbuf,"%s%s%s",buffer,name,ptr+6);
puts(newbuf);
continue;
}
ptr = (char *)strstr(buffer,":INPUTCOLOR:");
if (ptr)
{
*ptr = '\0';
sprintf(newbuf,"%s%s%s",buffer,color,ptr+12);
puts(newbuf);
continue;
}
puts(buffer);
}
fclose(fp);
}
4. Stateless vs statefull
기존의 인터넷 응용들(telnet, ftp 등)이 클라이언트와 서버가 계속 정보를 유지하는 statefull
인 방식과 달리, WWW은 stateless를 기반으로 운영되므로 클라이언트와 서버간에 정보가 유지
되지 않는다. 이는 WWW를 이용하여 응용프로그램을 작성하려는 프로그래머에게 또 다른 짐을
지워주게 되었다.
stateless를 statefull처럼 보이게 하기 위해 여러가지 편법들이 동원되고 있다.
과거의 응용프로그램들은 in-memory를 사용하여 이러한 정보들을 유지하였지만, CGI프로그
램에선 file system을 사용하여 이러한 정보들을 유지할 수 밖에 없다.
다음 예제는 간단한 형태로 statefull을 구현 하기위한 기본적인 틀만 보여 준다.
실제적으로 응용프로그램을 작성하기 위해선, 더 많은 정보들이 주고 받아져야만 하며, 이는
외부 화일을 통해서 구현될 수도 있고, 메시지 큐등을 사용하여 좀 더 고급스럽게 구현할 수도
있다.
<프로그램 1 : daemon.c>
#include
#include
main()
{
int pid;
fprintf(stdout,"Content-type: text/html%c%c", 10, 10);
fprintf(stdout,"\n Server is starting....");
fclose(stdout);
system("/home8/armian/public_html/cgi-src/statefull/server/server&");
}
<프로그램 2 : server.c>
#include
#include
static int second;
main()
{
FILE *fp;
int pid;
second = 0;
fp = fopen(SERVERINFO,"w");
pid = getpid();
fprintf(fp,"%d",pid);
fclose(fp);
signal(SIGUSR1,c_action);
for(;;)
{
sleep(4);
second++;
if (second > 9) break;
}
exit(0);
}
c_action()
{
int pid;
FILE *fp;
char buffer[100];
fp = fopen(CLIENTINFO,"r");
fgets(buffer,100,fp);
fclose(fp);
pid = atoi(buffer);
second = 0;
kill(pid, SIGUSR2);
signal(SIGUSR1,c_action);
}
<프로그램 3 : client.c>
#include
#include
#include
int s_action();
int pid;
main(argc,argv)
int argc;
char *argv[];
{
int mypid;
FILE *fp;
char buffer[100];
printf("Content-type: text/html%c%c", 10, 10);
fp = fopen(SERVERINFO,"r");
fgets(buffer,100,fp);
fclose(fp);
pid = atoi(buffer);
mypid = getpid();
fp = fopen(CLIENTINFO,"w");
fprintf(fp,"%d",mypid);
fclose(fp);
kill(pid, SIGUSR1);
signal(SIGUSR2,s_action);
sleep(4);
printf("\nPID of Server : %d",pid);
printf("\n
Server is dead....");
exit(0);
}
s_action()
{
printf("\n
PID of Server : %d",pid);
printf("\n
Server is alive....");
exit(0);
}
참고문헌
[NCSA95a]
The Common Gateway Interface
URL:
http://hoohoo.ncsa.uiuc.edu/cgi
[Leeds95a]
CGI Tutorial
URL:
http://agora.leeds.ac.uk/nik/Cgi/start.html
[WWW-KR95a]
가자, Web의 세계로! ,225p-231p
[홍릉91a]
UNIX 시스템 프로그래밍