CGI 로 내맘에 꼭 드는 게시판 만들기

김호성 / 동국대 네트워크팀 징검다리

 

     

    실용성 있는 CGI 로 게시판을 만들어 보기로 하자. 리눅스 래드햇에서 구현하는 SSI와 DB(Postgre) 연동을 통한 CGI로 게시판 만들기

     

1. 개요

    CGI로 맘에 꼭 드는 게시판을 만들어 보기로 하자. 이번 호에서는 만들기 위해서 필요한 배경지식과 간단한 라이브러리 사용법에 대해서 알고 넘어가기로 하자. 또 시간이 남으면 만들고자 하는 게시판의 구성에 대해서도 잠깐 언급하고 넘어가도록 하겠다. 그러면 머리가 복잡할 때 이렇게 간단한 CGI를 짜면서 기쁨을 얻기 바라겠다.

     

2. 기능성보다는 실용성 있는 CGI에 대한 이야기.

    CGI(Common Gateway Interface) - 불과 1년 전만 해도 무척이나 생소했던 단어였는데. 어느덧 CGI 사장설까지 나왔다. 무척이나 기술변화가 빠르다는 것을 실감하게 된다. 하지만 아직도 쓸 만한 기술이 CGI인 듯 싶다. 사용자에게 크게 부담감 주지 않고 기다리는 시간에서 자유로울 수 있다는 장점이 있다.

    자바가 많이 발전해서 충분한 기대와 또 가능성이 현실로 많이 다가 왔지만 아직도 DataBase와 관련된 부분에서 CGI 기술이 많이 활용되고 있다. 물론 DataBase를 다룰 때 상태유지하기 위한 노력이 필요하기는 하지만 약간 안정성 있는 DataBase 엔진을 사용한다면 쓸 만한 Application을 만드는데 그리 많은 노력이 필요하지 않다. 여러모로 CGI는 많이 알려져 있고 또 부담없이 개발할 수 있는 프로그램 인터페이스인 듯 싶다. 왜 CGI가 쉬운가? 이유는 간단하다. 출력의 경우 MIME헤더만 출력해주면 나머지 다른 부분은 일반 프로그램을 짜는 것과 동일하고 다만 CGI 입력에 대한 Decoding 만이 문제인데 그것도 라이브러리들을 이용해서 간단하게 해결할 수 있기 때문이다.

    여기서는 유명한 프로그램 기법이기 때문에 원론 적인 얘기는 많이 생략하고 바로 활용할 수 있는 것들을 중심으로 이야기를 풀어 나가고자 한다. 만일 원론적인 내용이 알고 싶다면 http://hoohoo.ncsa.uuic.edu/cgi/ 를 확인해 보라. CGI 에 대해서 부정적인 견해를 많이 가지고 있는 사람이라면 이 글의 이쯤에서 글을 그만 읽는다고 해서 손해 날 것은 없다고 본다.

    무엇보다도 필자가 이 지면을 통해서 잠깐 말하고 싶은 것이 있는데 그것은 바로 content에 관한 것이다. 신 기술만을 가지고 인터넷상(아니 어쩌면 프로그램세계에서)에서 무언가를 꿈꾼다는 것은 무리가 있기 때문이다. 우리나라 여건상 기술만을 개발하기만 한다고 해서 이득 볼 일은 많이 없다고 느껴진다. 어쩌면 가장 중요한 일은 기존에 개발되어있는 좋은 기술이 있다면 그것을 활용해서 인터넷상의 유저들을 끌어 모으는 일일 것이다. 좀더 깜찍하고 상큼한 아이디어로 사용자들을 매료시키는 일이야 말로 웹상에서 Application을 개발하거나 Homepage를 제작하거나 할 때 생각해 두어야 할 일이 아닐까?

    필자가 만드는 게시판이 그렇게 상큼하고 매력적이라고 말할 수는 없지만 대부분의 웹보드(웹게시판)프로그램에서 간과하고 넘어가는 부분에 기능을 추가하여 보았다.

     

3. CGI와 관련해서 알아두면 가끔은 쓸 만한 기술 SSI(Server Side Include)

    푸쉬기술 이전에 주로 사용되던 것이 바로 이 기법이다. 이것을 Html에서 웹서버에 있는 명령을 실행하거나 파일등을 include 할 때 사용하는데 명령어나 CGI 프로그램을 실행시키는 것은 약간 무모한 짓이기 때문에  파일만을 include시키는 것만을 사용하기로 한다.

    SSI 파일은 보통 확장자가 .shtml를 끝나는 파일을 많이 사용한다. .html을 바로 사용하지 않는 이유는 SSI를 이용하게 되면 문서를 일일이 파싱해야 하기 때문에 서버에 부담을 주기 때문인데 가급적이면 확장자를 구분해서 사용하도록 하고 자제해서 사용하는 것이 좋고 만일 사용하지 않을 수 있는 다른 아이디어가 있다면 그것을 이용하는 것이 좋다.

    어쨌든 필자가 이것을 이용해서 하고 싶은 일은 게시판에서 만들어낸 동적인 출력물을 html에 포함 시키기 위한 것이다. SSI가 작동하기 위해서는 httpd 의 srm.conf 와 access.conf 파일을 수정해서 SSI 가 되도록 한다.

    access.conf 파일의 일부분을 보면 httpd 도큐멘트 root가 있는데, 그 곳의 부분이 아래와 같이 되어 있을 것이다. Option 부분에 include를 추가 시켜주면 된다. 아래는 이미 추가가 되어 있는 상태이다.

    < Directory /var/www/docs >
    Options Indexes FollowSymLinks MultiViews Includes

    AllowOverride None
    order allow,deny
    allow from all
    < /Directory >

    srm.conf 파일에서는 중간쯤에 다음과 같은 항목이 주석표시(#)가 되어 있다면 없애주도록 하고 중간부분에 AddHandier server-parsed 항목을 찾아서 아래와 같이 수정해 주면 된다.

          :
          :
    # To use server-parsed HTML files
    AddType text/html .shtml
          :
          :
    AddHandler server-parsed .shtml

    이제 SSI가 작동될 준비는 끝이 난 것이다. httpd을 다시 구동시키고(ps -ax | grep httpd를 실행시킨 다음 가장 작은 번호의 httpd 의 PID를 가지고 kill -HUP httpd 의 PID를 하면 된다.) 간단하게 SSI 관련 명령어에 대해서 살펴보기로 하자.

    SSI 명령어는 html 의 주석인 <!---->사이에 들어간다. 자, 그럼 우리에게 필요한 것은 파일을 include 하는 것이므로 한번 확인해보도록 하자.
    파일을 include 하기 위해서는
    < !-- include virtual="./ include.html"-- >
    하면 된다. 여기서 제대로 된 경우라면 html 중간에 파일이 출력되는 것을 확인할 수 있을 것이다.

     

4. 게시판을 만들기 전에 필요한 DB연동에 대한 이야기.

    게시판을 만드는데 DataBase를 연결해야 하는가? 그게 의문점으로 남을지 모르겠다. 왜냐하면, File I/O를 이용해서 직접 구현할 수도 있는데 말이다.

    장단점이 있다. 일단 File I/O를 사용하게 되면 빠르게 구현할 수 있지만 복잡한 정렬기능이라든가 데이터의 일부분을 가져오는데 많은 코드가 필요하게 되는 단점이 있다. 간단한 방명록을 만드는 경우라면 File I/O를 사용하는 것이 오히려 효과적일 수도 있다. DataBase를 연동하게 되면 덩치는 커지는 대신 다양한 자료관리 기능을 제공해 준다. 필자는 DataBase를 직접 연동한다. 이유라면, 편리하게 프로그램이라든가 데이터를 관리할 수 있기 때문이다. 또한 Application을 개발하는데 들이는 시간을 절약할 수 있기 때문이다. 또한 어떤 DataBase 엔진이건 간에 API를 사용하는 방법이 거의 비슷하게 구성되어 있기 때문에 하나를 제대로 쓸 줄 아는 사람이라면 다른 DataBase 엔진에서 제공해주는 API 라든가 JDBC를 사용할 때도 쉽게 전이가 가능하다.

    CGI 로 데이타를 처리할 때 한가지 주요하게 기억해 두어야 할 것은 WEB 의 특성인 상태없음 (stateless) 이다. 말하자면 프로그램이 한번 접속해서 데이터를 받아오면 다음에 접속할 때 프로그램을 처음부터 다시 시작한다는 것을 의미한다. 이것은 무엇을 의미하는가? 결국 중간에 어떤 결과 값을 가져오고 난 다음 상태를 기억했다가 일부분을 연속적으로!! 본다는 것은 불가능하다는 소리가 된다. 결국 이것을 구현하기 위해서는 여러 가지 변수에 상태 값을 CGI에서 CGI로 넘겨줌으로써 계속 상태를 유지하는 것처럼 동작하게 하는 일종의 트릭을 사용해야 한다.

    이 성질(stateless) 때문에 CGI와 DataBase 연동의 문제는 매번 프로그램의 재시작을 함으로써 서버족에 오버헤드가 발생한다는 것이다. 어쩌면 이것은 CGI 의 한계일지도 모른다. 물론 JAVA를 쓴다면 이 문제는 해결할 수도 있다. 하지만 서버가 충분히 데이터를 다룰 수 있을 만큼의 퍼포먼스를 내고 최소한 UNIX 환경이라면 그렇게 걱정할 것이 없다. 펜티엄 100에 32M의 메모리를 가졌다면 우리가 일상에서 사용하고 있는 왠만한 중소급 data를 처리하는 데에는 그렇게 큰 걱정될 것은 없다고 본다.

     

5. CGI Library - cgihtml - 1.66

    왜 Library를 사용해야 하는 건지?
    자, 그러면 CGI와 일반 Application을 구분하게 하는 특징은 바로 CGI 의 표준 입출력이 httpd에 연결되어 있다는 것이다. 그러므로 stdin, stdout으로 어떤 것을 출력하거나 입력 받는다면 그것은 httpd와 통신을 하게 되는 것이다. 그러므로 httpd 와 입출력하기 위해서 몇 가지 약속 해둔 것이 있는데 그 약속을 지키면서 httpd 와 data를 주고 받는 것이 CGI 의 전부이다.
    입출력을 처리하는 것은 간단하기는 하지만 여시 복잡한 layout 의 프로그램을 개발하기에는 기본적으로 hoo.hoo.ncsa.uuic.edu에서 제공해주는 기본 틀만 가지고는 부족한 면이 있다.
    조금 신경을 쓰면 자기 자신에게 꼭 마음에 드는 라이브러리를 만들 수는 있을 것이다.

    여기서 다시한번 말하지만 프로그램을 개발할 때 어디에 중점을 맞추고 있는가를 잊으면 안된다. 만일 CGI 자체 구현이 목적이라면 그것에만 신경을 써야 할 것이고 Application 이 목적이라면 Application 에만 신경을 쓸 수 있도록 자신의 환경을 만들어야 한다. 가끔 가다가 이상한 자존심(?)을 발휘하는 사람들이 있어서 모든 것을 자신의 손으로 개발해야 한다는 식의 사고방식으로 시작하려고 하는 사람들이 있다는 것이다. 아무튼 여기서 우리의 목적은 Application을 개발하는 데 있다. 자신이 Application을 개발하는데 있어서 중요한 Content를 구현하는데 전력을 다할 수 있어야 하는 것이다.

    필자가 이 라이브러리를 이용하는 이유는 여러 가지가 있다. Text 상태에서 직접 데이터를 입력하고 디버깅할 수 있기 때문에 웹에서의 골치 아픈 Debugging 작업에서 벗어나게 해 주기 때문이다. 이 라이브러리에 대해서 설명하기 전에 웹에서의 작업을 잠시 얘기해보자. 왜 골치 아픈 Debugging 인가? 그냥 Application 에서와 달리(특히 UNIX 에서) CGI에서는 여러 가지 부분에서 Error 가 발생할 수 있기 때문이다. 그리고 에러가 나면 httpd 는 아주 무성의한 error message 만을 내고 만다. error debug를 하기 위해서 할 수 있는 가장 원시적인 방법인 print를 이용하는 것을 허용하지 않는다. 결국 중간에 어디에선가 error 가 나면, 기존에 나오는 모든 출력이 무시되고 httpd error message 만 나오기 때문이다. 그렇기 때문에 이게 httpd에서 난 것인지, Application에서 난 것인지 잘 알 수가 없다.

    UNIX에서 Debugging Tool을 따로 쓰지 않는 사람이라면 이렇게 error debugging 하는 것은 정말 머리털 빠지는 일일 수밖에 없다. 더군다나 CGI 만 개발하고 httpd 에 대해서 잘 모른다면 정말 속수무책 이다. 보통 error 가 발생하는 경우는 httpd의 특성상 주어지는 디렉토리의 권한 문제에서 날 수가 있고 아니면 UNIX 의 디렉토리 권한문제, 또는 CGI 코드에서 아니면 우리가 연동해서 쓰고자 하는 DB 엔진의 setting 문제 일 수도 있다. 이렇게 여러 가지 경우의 수를 다 검사해보아야 하는데 이런 상황을 잘 모르는 상태에서 프로그래밍을 하다가 보면 짧은 몇 줄 짜고 Debugging 하는데 날 밤 꼴딱 새는 것은 정말 우스운 일이 되는 것이다.

    이런 경우에 CGI를 개발하는데 의욕을 상실하게 되고 우리가 원하는 목적을 제대로 달성할 수 없게 되는 것이다. 만일 여기서 Web Broswer 만 어떻게 벗어난다면 그나마 Debugging 하기는 편리할 것이다.

    cgi-html도 Text 모드에서 직접 CGI 입력변수를 넣을 수 있도록 해준다. 그렇게 함으로 얻을 수 있는 장점은 바로 httpd의 무시무시한 server error message를 피할 수 있다. 이렇게 debugging 하는 것이 100% 완벽하지는 않았지만 필자의 경험으로는 90% 정도는 효과가 있는 듯 싶다.

    그러면 이제 cgi-html 에 대해서 잠깐 설명을 하고 넘어가기로 하자.

    기본적인 구성 프로그램 흐름은 다음과 같다. 자세한 library 설명은 다음 글에서 소개하도록 하고 여기서는 어떻게 사용하는가만 확인해 보도록 하겠다.

    1  /* template using cgihtml.a library */
    2  #include <stdio.h >   /* standard io functions */
    3  #include <signal.h >  /* this and unistd.h for signal trapping */
    4  #include <unistd.h >
    5
    6  #include "cgi-lib.h"   /* CGI-related routines */
    7  #include "html-lib.h"   /* HTML-related routines */ 
    8  #include "string-lib.h"   /* 문자열, 메모리 안전하게 할당하는 함수 */ 
    9  int main()
    10  {
    11  llist entries; /* define a linked list; Encoding 된 자료를 Decoding 해서 리스트로 구성 */
    12  char *name ;
    13  char *safe_name ;
    14  /* Decoding 하는 함수 */
    15  read_cgi_input(&entries) ;
    16  name = cgi_val(entries, "name") ;/* 포인터만 넘길 경우 그것이 다른 함수로 return 되는       것은 위험하다 */
    17  safe_name = newstr(cgi_val(entries, "name")) ; /* 메모리를 할당하는 것이 안전하다 */
    18  html_header() ; /* print HTML MIME header ;puts("Content-type:text/html\n\n");      과 동일 */
    19  /* 이곳에 원하는 작업을 수행한다 */
    20  html_end() ;  /* send appropriate HTML end footers () */
    21  list_clear(&entris) ;  /* 링크드 리스트 없에 주는 것 잊지 말기 */
    22  return 0 ;
    23  } 

    cgi-html 에서는 3개의 header 파일을 제공해주는데 cgi-lib.h는 cgi 입력변수를 파싱하는데 필요한 함수들이 정의가 되어있다. 입력변수들을 처리하기 위해서 llist 라는 구조체를 이용하여 linked list를 사용한다. html-lib.h가 있는데 이것은 html tag와 관련이 있는 함수들을 정의 해 두었다. 다음 string-lib는 문자열 함수를 처리하는 데 필요한 함수들을 제공해주고 있다. 특히 많이 사용할 함수가 있는데 그것은 문자열 메모리 할당 함수이다.

    만일 CGI 입력변수를 처리하는 경우라면 반드시 llist 구조체를 반드시 선언해준다. 11번째 줄에서 entries 라는 구조체를 선언해주었다. 그 다음 CGI로 들어오는 입력을 처리해주기 위해서 읽어주는 작업이 필요하다. 15번째 줄을 보면 read_cgi_input 함수를 사용하였다. 이것을 통해서 linked list 에 CGI 입력 값을 저장한다. 그 다음엔 CGI 변수의 값을 필요할 때 불러서 쓰기만 하면 된다.

    CGI 변수를 부르는 함수로 cgi_val() 함수를 쓴다. CGI 입력 값 중에서 변수명을 넣으면 리스트에서 찾아서 그 값을 리턴해준다. 문자열 값으로 리턴이 되는데, 여기서 주의해야 할 사항이 있다. 16, 17 번째 줄을 살펴보자. 둘 다 제대로 값이 들어간다. 그런데 16 번째 줄처럼 포인터를 이용해서 값을 받을 때 제대로 값을 전달되지 않는 경우가 있으므로 17 번째 줄처럼 cgi_html에서  제공하는 newstr 함수를 써서 메로리 할당한 다음 자동으로 스트링을 복사하던지 아니면 문자배열을 선언한 다음 strcpy 명령으로 복사를 해서 쓰는 것이 낫다. 어쨌든 17 번째 줄처럼 사용할 것을 권장한다.

    html_header() 는 html MIME type을 출력하는 것인데 puts("Content-type : text/html\n\n") ; 과 똑같은 결과가 된다. 한가지 기억해 두어야 할 것은 HTML 형식으로 출력하려고 한다면 반드시 넣어주어야 한다. 그렇지 않으면 httpd 가 어떤 종류의 문서를 출력해야 할지 몰라서 황당한 server error를 내고 말 것이다.

    이렇게 한 다음 나머지 처리하고 싶은 것을 처리한 다음 list 구조체의 메모리를 해제해주어야 한다. 21번째 줄처럼 list_clear() 함수를 이용하면 된다. 주의할 것은 &entries을 파라메터로 넘겼다는 것이다.

    일반 프로그램에서 추가되는 것이 얼마 되지 않는다는 것을 알 수 있다. 컴파일하는 방법은 다음과 같다.

      cc -o program.cgi program.cgi.o cgihtml.a

    컴파일러는 cc 또는, gcc  를 사용하면 된다. 여기서 -o 옵션을 쓰면 생성하고 싶은 파일이름을 지정하는 것이고 cgihtml.a 라이브러리 archive를 포함시켜 주면 된다.

    cgi-html 라이브러리는 아래의 사이트에 가면 받을 수 있다.

    eekim.com FTP Site    ftp://ftp.eekim.com/users/eekim/cgihtml/

    Harvard Computer Society FTP Site    ftp://hcs.harvard.edu/pub/web/tools/cgihtml/

    cgi-html 홈페이지가 있는데 이곳에 가면 취근의 정보를 얻을 수 있으니까 한번 가보도록 하라.

    http://www.eekim.com/software/cgihtml/.

    cgi-html 함수 레퍼런스를 실어보았다. 참고가 되었으면 좋겠다.

     

    cgi_html 함수 레퍼런스-cgi-lib.h에 포함되어 있는 것들

      Library 변수

      cgi-lib.h CGI

      SERVER_SOFTWARE
      SERVER_NAME
      GATEWAY_INTERFACE
      SERVER_PROTOCOL
      SERVER_PORT
      REQUEST_METHOD
      PATH_INFO
      PATH_TRANSLATED
      SCRIPT_NAME
      QUERY_STRING
      REMOTE_HOST
      REMOTE_ADDR
      AUTH_TYPE
      REMOTE_USER
      REMOTE_IDENT
      CONTENT_TYPE
      CONTANT_LENGTH
      HTTP_USER_AGENT

      Library 함수들

      short accept_image()
      브라우져가 이미지를 지원해 주는가 알아보는 함수. 지원해준다면 1을, 그렇지 않다면 0을 리턴함.

      void unescape_url()
      escape 된 CGI 입력 변수의 값(escaped URL values)을 원래 값으로 바꿔주는 함수. 우리가 이 함수를 직접 쓸 일은 없다.

      int real_cgi_input(llist *entries) ;
      인코딩 된 CGI 자료들을 디코딩 하면서 llist 에 저장한다. GET, POST 두 가지 중 아무 경우라도 알아서 선택해서 파싱(parsing)해준다.

      char* cgi_val(llist 1, char *name) ;
      주어진 변수 이름에 해당하는 값을 찾아서 리턴해준다. 이름이 없다면 NULL을 리턴해 준다.

      char **cgi_val_multi(llist 1, char *name) ;
      check box를 처리할 때 쓰인다. 이름이 없다면 NULL을 리턴한다.

      char* cgi_name(llist 1, char *value) ;
      정해진 값으로 이름 찾을 경우 사용한다. 쓸일이 있는 함수인지는 잘 모르겠다.

      char **cgi_name_multi(llist 1, char *value) ;
      값으로 찾는 것만 제외하고 cgi_val_multi()와 비슷한 기능이다.

      int parse_cookies(llist *entries) ;
      HTTP_COOKIE 변수를 parsing 할 때 사용한다.

      void print_cgi_env() ;
      CGI 환경변수를 찍어주는 함수이다. 환경변수가 존재하지 않는 것이 있다면 (null)이 찍힌다.

      void print_entries(llist 1) ;
      llist 구조체 안에 있는 값을 찍어준다.

      char* escape_input(char *str) ;
      모든 metacharacter를  \로 escaping 해주는 기능을 수행한다.

      short is_form_empty(llist 1) ;
      이름이 없는 것을 sumit 했는가 살펴볼 때 사용한다.

      short is_field_exists(llist 1, char *str) ;
      필드값이 존재하는가 검사한다. NULL을 리턴하면 존재하지 않는 것이다.

      short is_field_empty(llist 1, char *str) ;
      필드가 존재하지 않거나 필드값이 없다면 1을 리턴한다.

     

    -html-lib.h : html 과 관련된 함수들  

      Library functions

      void html_header() ;
      HTML MIME 타입을 찍어주는 함수.

      void mime_header(char *mime) ;
      사용자가 이의의 MIME 타입을 출력하고자 할 때 사용한다.

      void nph_header(char *status) ;
      No Content 헤더를 보낼 때 사용한다.

      void show_html_page(char *loc)
      html 파일을 삽입하고 싶을 때 사용한다.

      void status(char *status) ;
      HTTP 상태 헤더를 보낼 때 사용한다.

      void pragma(char *msg) ;
      HTTP pragma 헤더를 보낼 때 사용한다.

      void set_cookie(char *name, char *value, char *expires, char *path,
                             char *domain, short secure) ;
      주어진 파라메터로 쿠키를 지정할 때 사용한다.

      void html_begin(char *title) ;
           <html> <head>
           <title> title </title>
           </head>
           <body>을 찍어주는 함수이다.

      void html_end() ;
            </body> </html>을 찍어주는 함수이다.

      void h1(char *header) ; ... void h6(char *header) ;
            <H?>을 찍어주는 함수이다.

      void hidden(char *name, char *value) ;
      form에서 hidden type을 출력할 때 사용한다.

     

    - string-lib.h : 문자열 처리와 관련된 함수이다.

      Library functions

      char* newstr(char *str) ;
      메모리를 할당하고 주어진 문자열을 복사해서 리턴해주는 함수이다. 제대로 복사를 해주니까 문자열 파괴의 염려가 없으므로 자주 사용하면 좋다.

      char* substr(char *str, int offset, int len) ;
      문자열을 찾고자 할 때 사용한다.

      char* replace_ltgt(char *str) ;
      <, >를 &lt, &gt 로 변환해 주는 함수이다.

      char *lower_case(char *buffer) ;
      주어진 문자열에서 대문자를 소문자로 바꾸어주는 함수이다.

       

    query 의 결과를 텍스트상태에서 확인하는 방법

    command line 상에서 프로그램을 실행시키면 아래와 같은 결과가 나온다.

      --- cgihtml Interactive Mode ---

      Enter CGI input sting. Remember to encode appropriate characters.
      Press ENTER when done :

    입력문자열이 다음과 같다고 가정한다면

      name=eugene&age=21

    query의 결과로 다음과 같은 것이 출력될 것이다.

      Input string : name=eugene&age=21
      String length : 18
      --- end cgihtml Interactive Mode ---

      <h1>Quey results</h1>
      <dl>
          <dt> <b>name</b>
          <dd> eugene
          <dt> <b>age</b>
          <dd> 21
      </dl>
      </body> </html>

    segmentation fault가 일어난다면 이렇게 파일을 실행시켜 봄으로써 프로그램의 error를 잡을 수 있을 것이다.

     

6. Postgres C Library - libpq

    리눅스 레드햇을 설치했을 경우 포스트그래스의 디렉토리와 실행환경에 대해서 설명을 해주고 인스톨본을 설치했을 경우의 환경을 설명해 준다.

    포스트그래스에 대한 내용은 너무나 유명한 내용이기 때문엔 필요한 API 만을 설명한고 나머지는 reference를 참고 하도록 한다. 참고로 LIBPQ에 대한 아래의 설명은 www.linuxlab.co.kr에 있는 강좌파일을 참고 했음을 밝혀 둔다.

    -LIBPQ는 Postgres에 대한 API로 사용자 프로그램은 헤더화일로 libpq-fe.h를 include 하고 library로 libpq.a를 link 하여 작성한다.

      /usr/local/pgsql/include/libpq-fe.h
      /usr/local/pgsql/lib/libpq.a

    - 환경변수와 초기화(setenv 함수를 이용설정?)

      PGHOST : Postgres 가 탑재된 서버이름 설정

      PGOPTIONS : 부가적인 runtime option

      PGPORT :포트번호 설정

      PGTTY :서버로부터의 디버깅 메시지를 출력할 파일이나 화면(tty) 설정

      PGDATABASE : POSTGRES 데이터베이스 이름 설정

      PGREALM : POSTGRES 와 사용할 Kerberos realm 설정

    - API

      1) 데이터 베이스 연결/종료 : PQsetdb, PQfinish, PQreset

      PGconn *PQsetdb
      (char *pghost, char *pgport, char * pgoptions, char *pgtty, char *dbName) ;
      - argument 가 없으면, 환경변수 값을 사용 환경변수도 없으면,
         hardware default 값을 사용

      void PQfinish(PQconn *conn)
      - 연결을 해제하고, PGconn struct pointer를 free.(이후에 사용할 수 없다)

      void PQreset(PGconn *conn)
      - 재 연결

      2) 연결검사 : PQstatus(PQdb, PQhost, PQoptions, PQport, PQtty)

      ConnStatusType *PQstatus(PGconn *conn)
      - 커넥션의 status 리턴(CONNECTION_OK/CONNECTION_BAD)

      char *PQdb(PQconn *conn)
      - 해당 커넥션의 database 이름을 리턴

      char *PQhost(PQconn *conn)
      - 해당 커넥션의 host 이름을 리턴

      char *PQport(PQconn *conn)
      - 해당 커넥션의 port 이름을 리턴

      char *PQoptions(PQconn *conn)
      - 커넥션에 option 사용된 을 리턴

      char *PQtty(PQconn *conn)
      - 켜넥션에 사용된 tty를 리턴

      3) 에러메세지 관련 함수

      char *PQerror Message(PQconn *conn) ;
      - 커넥션과 관련된 에러메세지 리턴

      void PQtrace(PQconn *conn, FILE*debug_port) ;
      - frontend 와 backend server 간의 message 를 추적가능하게 하고,
         debug_port로 출력되게 함.

      void PQuntrace(PQconn *conn) ;
      - frontend 와 backend server 간의 message 를 추적할 수 없도록 함

      4) 쿼리(Query)

      PGresult *PQexec(PQconn *conn, char *query) ;
      - query 한 결과를 PGresult structure 에 저장(structure 의 필드를
         직접 억세스 하지 않고, 이하의 함수를 써서 결과를 분석하는 것이 좋다.)
      - Null 을 리턴할 경우 PQerror Message 함수 이용

      ExecStatusType PQresult structure(PGresult *res) ;
      - query 의 결과를 분석
      - 리턴값 :
      PGRES_EMPTY_QUERY,
      PGRES_COMMAND_OK, /* query 가 command 일 경우 */
      PGRES_TUPLES_OK, /* 성공적으로 tuple 을 리턴한 경우 */
      PGRES_COPY_OUT,
      PGRES_COPY_LN,
      PGRES_BAD_RESPONSE, /* 예기치 못한 결과가 발생할 경우 */
      PGRES_NONFATAL_ERROR,
      PGRES_FATAL_ERROR

      ◆query 한 tuple(instance, record)결과를 추출하는 함수
         (PGRES_TUPLES_OK 리턴시)

      int PQntuples(PGresult *res) ;
      - 쿼리 한 결과의 tuple 의 수를 리턴

      int PQnfields(PGresult *res) ;
      - 쿼리 한 결과의 field(attribute)의 수를 리턴

      char *PQfname(PGresult *res, int field_index) ;
      - 현재(?) tuple 의 field_index 번째 (0 부터 시작) 필드명을 리턴

      int PQfnumber(PGresult *res, char* field_name) ;
      - 현재 tuple 의 field_name의 필드명을 가진 필드의 index를 리턴

      Oid PQftype(PGresult *res, int field_num) ;
      - 현재 tuple의 field_num 번째 필드의 type(Object ID)을 리턴

      int2 PQfsize(PGresult *res, int field_index) ;
      - 현재 tuple의 field_index 번째 필드의 size(in byte)를 리턴
      - 1 이면 가변길이형

      char *PQgetvalue(PGresult *res, int tup_num, int field_num) ;
      - tup_num 번째 tuple 의 field_num번째 필드의 값을 리턴
      - 대부분의 경우 결과 값은 null-terminating ascii 문자열로 변환되어 리턴 됨
      - binary cursor 커리의 경우는 backend server의 내부적인 바이너리 형식으로
         리턴되므로 프로그래머가 알맞은 C 타입으로 변환해야 함.
      - 결과 값은 PGresult 구조체의 멤버에 대한 포인터이므로,
         strcpy 등으로 복사하여 사용한다.

      int PQgetlength(PGresult *res, int tup_num, int field_num) ;
      - tup_num 번째 tuple 의 field_num번째 필드의 size(in byte)를 리턴
      - 구조체인 경우, member 의 크기를 포함하지 않는다.

      char *PQcmdStatus(PGresult *res) ;
      - 최근 query 에 관계된 command status 를 리턴

      char *PQoidStatus(PGresult *res) ;
      - 최근의 query 가 insert 문이었을 경우 삽입된 tuple 의 Oid에 관한 문자열 리턴,
         그렇지 않으면 empty string.

      void PQprintTuple(PGresult *res) ;
                                FILE* fout, /* output 파일 */
                                int printAttName /* 필드이름을 출력할지 여부 */
                                int terseOutput, /* delimiter 로 bar를 사용할 것인지 여부 */
                                int width /* 컬럼의 폭(0 이면 가변 폭) */
                                ) ;
      - 모든 tuple 을 파일(일반화일, stdout)으로 출력(psql이 이 함수를 씀)
      - 필드명을 출력하게 할 수도 있음

      void PQclear(PQresult *res) ;
      - 해당 PQresult structure를 메모리로부터 삭제
      - query 결과를 더 이상 참조하지 않을 때 반드시 해주어야 함(메모리 누수방지)

      5) Fast Path : db내의 internal function호출

      PGresule* PQfn(PGconn,conn)
                             int find, /* 호출하고자 하는 함수의 oid */
                             int *result_buf, /* 결과 값을 저장할 버퍼(프로그래머 제공) */
                             int *result_len, /* 결과 값의 길이 */ 
                             int *result_is_int, /* 결과값이 정수형인지 여부 */ 
                             PQArgBlock *args, /* 함수에 전달할 아큐먼트들 */ 
                             int nargs) ;, /* 아큐먼트 갯수 */ 
      - security hole 이 생길 수 있으므로 일반적으로 잘 쓰이지 않음
         typedef struct {
              int len ;
              int isint ;
              union {
                  int *ptr ;
                  int interger ;
              } u ;
         } PQArgBlock ;
      - PGresult 구조체는 적당한 시점에 free해준다.

      6) Asynchronous Notification

      PGnotify* PQNotifies(PGconn *conn) ;

      7) COPY 명령과 관계된 함수들

      * copy : libpq에 의해 사용된 커넥션에 /으로 데이터를 쓰거나, 읽어오는 프로토콜 이하 함수들은 이 커넥션에 direct로 붙어서(일반적인 PQexec으로는 실행되는 query는 하나의 논리작업 단위인데, 이하 함수 호출들은 여러 번의 함수호출이 하나의 논리작업단위를 형성한다) 이 명령어를 지원한다. 즉, PQexec으로 copy 명령을 실행시키고, 바로 이어서 direct read/write 수행
      - 형식 : copy [to/from]

      int PQgetline(PGconn *conn, char *string, int length) ;
      - copy ~ to stdout 명령을 실행한 후 명령의 결과를 얻어옴
      - 커넥션으로부터 newline으로 끝나는 문자열을 읽어서 string 에 저장하고
         newline을 NULL 바꿈(즉, 최대 length-1만큼 읽어옴)
         - 리턴값은 0 (newline까지 전체라인이 읽혔을 때)
            1 (truncate 되었을 때)
         - backend server로부터 copy의 결과를 모두 전송했을 때는 "."만으로 된 라인을
            전송한다.

      void PQputline(PGconn *conn, char *string) ;
      - 커넥션에 null-terminated string을 전송한다.
      - 전송이 끝났을 때는 "."으로 된 문장을 전송해야 한다.

      int PQendcopy(PGconn *conn) ;
      - copy 명령으로 형성된 direct communication 이 끝났음을 알리고,
         backend server와 sync 되게 한다.
      - 성공하면 0을 리턴한다.

      [ 예제 ]
      PQexec(conn, "create able foo (a int4, b char16, d float8)") ;
      PQexec(conn, "copy foo from stdin") ;
      PQputline(conn, "3hello world4.5\n") ; 
      PQputline(conn, "4goodbye world7.11\n") ;
      ...
      PQputline(conn, ".\n") ; 
      PQendcopy(com) ;

      7) 사용자 인증과 관련된 함수

      char *fe_getauthname(char* errorMessage) ;
      - 인증된 사용자의 이름을 담은 static space에 대한 포인터를 리턴
      - 인증된 사용자의 이름이 사용자의 /etc/passwd 엔트리나 환경변수등과
         다를 수 있으므로 getenv 함수나 getpwuid 대신 쓸 것을 권장한다.

      void fe_setauthsvc(char *name, char* errorMessage) ;
      - 통상 컴파일시 command-line switch로부터 설정된 디폴트의 인증서비스 이름대신
         name 으로 주어지는 서비스 이름을 사용토록 한다.
      - 에러의 경우 에러메세지를 errorMessage에 설정한다.

      이번 호에서는 앞으로 사용할 툴들에 대해서 잠깐 둘러 보았다. 내용이 그렇게 썩 잘 정리가 된 것은 아니지만 필자가 생각하여 정리한 어설픈 얘기들이 CGI로 프로그래밍 해보려고 하는 여러분에게 어느 정도는 도움이 될 것이라고 생각한다.

      다시한번 말하지만 프로그래밍을 공부할 때는 실용적인 것과 미래지향적이 있다는 것을 잊지 말기 바란다.




▲ top

home으로...