From YYpBD's MediaWiki
제 목:[강좌] 델파이로 만드는 CGI 0 .. 들어가기 전에 관련자료:없음 [656]
보낸이:배한백 (째즈토끼) 2000-02-23 20:39 조회:990
안녕하세요..
이번에 델파이로 cgi만드는걸 한번 써 볼려구 하는데요..
강좌란을 많이 기웃거리긴 했지만 직접 써 보는건 처음이라 잘될지 모르겠어요.
보나마나 중구난방식으로 앞뒤 안맞는 글을 쓰게될게 뻔하지만, 그래도 필요한
분에게는 도움이 될지몰라 써보기로 했습니다.
너무 손가락질은 하지말아 주세요.
요즘 델파이 5가 나왔는데요..
저는 델파이 4로 계속 cgi를 만들어 왔고, 5는 구경도 못해본 처지라 부득이하
게 델파이 4에 맞춰 글을 쓰겠습니다. 델파이 3에서 4로 오면서 별로 바뀐게
없었는데..(버그까지도 그대로..) 5에서는 어떻게 달라졌는지 궁금하네요.. 하지만
그렇게 차이는 없을것으로 생각됩니다.
원래는 여기에 강좌 순서를 적으려고 했는데, 괜히 순서 적어놨다가 그대로 못
하고 엉망이 될것같아 포기했습니다.
그냥 되는데로 쓰겠습니다.
이 강좌는 하이텔 홈페이지 동호회(iwebf)로 올라갑니다.
제 목:[강좌] 델파이로 만드는 CGI 1 .. 시작하기 관련자료:없음 [657]
보낸이:배한백 (째즈토끼) 2000-02-23 20:41 조회:719
1. 시작하기
어떤 프로그램을 만들자면 이름을 정하는데요, 일단 만들 cgi의 이름을 정
하세요. 이름을 babo 라고 정하고, 이 이름으로 계속 글을 쓰겠습니다.
먼저 델파이를 열고 "file"의 "New…"를 눌러보세요. 여러가지 이상한게 많
이 나오는데 cgi에 필요한건 딱 하나입니다. "Web Server Application" 바로
이놈입니다.
만일 이게 나오지 않는다면 그 델파이로는 만들 수가 없습니다.
(물론 만들 수도 있지만 제가 하는 강좌와는 무관합니다.)
cgi를 만드는데는 델파이 C/S 버전 이상이 필요합니다.
아이콘을 누르면 선택 창이 하나 뜨는데,
(1) ISAPI/NSAPI Dynamic Link Liberary
(2) CGI Stand-alone executable
(3) Win-CGI Stand-alone executable
중에 하나를 선택해야 합니다.
여기서 Win-CGI라는것은 윈도우용 CGI프로그램을 가리키는 것 같은데, 일
반적으로 거의 쓰지않는 방식입니다. 저두 한번도 사용해보지 않았기 때문
에 알 수가 없어요.
CGI는 도스용 프로그램이고 ISAPI는 뒤에 설명 그대로 DLL 파일입니다.
(이놈의 워드가 분명 "화"일이라고 쳤는데 "파"일로 바뀌네요.. 파일이 맞는
건가?)
CGI는 exe 파일이기 때문에 한번 호출될 때마다 메모리로 로딩되고 초기
화를 하게 되므로 상대적으로 느리다고 볼 수 있습니다. 그에 비해 dll 파일
인 ISAPI는 맨처음 한번 로딩되면 웹서버를 종료시키기 전까지 메모리에
붙어있기 때문에 처리속도가 엄청납니다. ASP처럼 인터프리터 시간도 가지
지 않고, 그야말로 웹서버의 일부가 되어버립니다.
하지만 프로그램 완성전까지는 상당히 불편하죠.. 한번 테스트해보고 웹서
비스 종료시키고 다시 웹서비스 시작하고 테스트하고…
다른 분들은 어떻게하는지 알 수 없지만 저 같은 경우는 이렇게 합니다. 개
발과정에서는 exe로 만들고 완성되면 dll로 서비스하고… 강좌도 이런식
으로 시작하겠습니다.
먼저 (1) ISAPI/NSAPI … 를 선택해서 새 프로젝트를 엽니다.
그리고는 아무것도 건들지 말고 모두 저장하기 버튼을 누릅니다.
유닛의 이름을 물으면 아무 이름을 붙입니다.
Ex) babo_Unit1.pas
프로젝트의 이름을 물으면 프로그램의 이름뒤에 _isapi를 붙여 저장합니다.
Ex) babo_isapi.dpr
그리고는 다시 새 프로젝트를 열기위해 "New…"의 Web Server Application을
선택하고 이번에는 (2) CGI …를 선택합니다.
그리고는 또다시 모두 저장하기 버튼을 누릅니다.
유닛의 이름을 물으면 isapi에서 적었던 그 이름을 붙입니다.
Ex) babo_Unit1.pas
파일 세개의 목록이 나오면서 덮어쓸까요? 물어보는데, 망설이지말고 덮어
씌웁니다.
그리고 프로젝트의 이름은 _isapi를 붙이지 말고 그냥 씁니다.
Ex) babo.dpr
그러면 이제 babo.dpr로 프로그램을 만들고 다 만든후에 babo_isapi.dpr을
불러 컴파일한 후 babo_isapi.dll을 babo.dll 로 이름만 바꾸면 되는겁니다.
아주 간단하죠.
하지만 명심할 점은 프로그램을 만들면서 항상 이게 ISAPI에서도 제대로
동작할지를 고려해야한다는 것입니다. cgi로 테스트하기 때문에 지나치기
쉬운부분인데, 전역변수나 전역 컴퍼넌트를 사용할때는 꼭 ISAPI를 고려해
야합니다.
ISAPI는 전역변수 및 전역 컴퍼넌트를 공유하기 때문에 사용자간의 충돌이
있을 수도 있습니다. 예를 들어 볼께요. TTable 컴퍼넌트로 회원들의 명단을
열어 놓고 해당 회원의 위치로 이동하여 작업하려는 순간 다른 회원이 들
어와서 그 테이블의 현재 위치를 바꿔버릴 수도 있다는 이야기가 되겠습니
다. 동시에 ISAPI를 엑세스하는 사용자 수를 1명으로 줄여서 서비스하면
충돌은 없겠지만, 이런 문제가 될 부분은 아예 없이 만드는게 좋겠죠..
자, 다시 진도를 나가기로 하죠.
새 프로젝트를 열면 코딩하는 창 외에 폼처럼 보이는 창이 WebModule1이
란 타이틀을 달고 하나 생깁니다. 이 위에서 마우스를 더블 클릭해보세요.
그러면 Editing WebModule1.Action 이란 창이 또하나 뜨게 되는데, 바로 여기
서 여러분은 대부분의 일들을 하게 될 겁니다.
여기서 왼쪽 위의 Add New 아이콘을 눌러보세요. WebActionItem1 이란게 하
나 생겼죠? 그 위에 커서를 맞추고 왼쪽의 프로퍼티 창을 보세요. 음.. 프로
퍼티 창은 "Object Inspector"이란 이름이 붙은 델파이 창입니다. 이상하게 제
가 프로퍼티 창이란 이름으로 알고 있는 관계로 다음에도 제가 프로퍼티
창이라고 하면 그 창인 줄 아세요. 이 창에는 다섯개의 퍼로퍼티와 하나의
이벤트가 있는데요.. 하나하나 설명을 드릴께요.
Default : 별로 필요가 없어요. 그냥 false로 두세요. 요 아래서 설명하죠.
Enabled : 이것두 필요없어요. 당연 True로 두시고요.. False로 해놓으면 없는
거나 마찬가지가 됩니다.
MethodType : 이건 html의 파라미터를 입력받는 방식인데, mtAny로 해놓으
면 알아서 합니다. 그냥 그렇게 하세요.
Name : 이름인데요.. 항목이 많아지면 찾기가 어렵기 때문에 아래의
PathInfo와 같은 이름을 쓰는게 좋을 것 같아요.
PathInfo : 요건 실제 html문서에 쓰이는 이름입니다. 서비스 성격에 맞게 이
름을 붙이세요. 만역 여기 "abc"라고 쓰면 (자동으로 앞에 슬래쉬가 붙어요)
홈페이지 상에서는 "http://…/babo.exe/abc?a=1&b=1…" 이런식으로 사용됩니
다.
"http://…/babo.exe"만 요청하거나 "http://…/babo.exe/abc"라는 요청을 했는데
abc라는 항목이 없다면… 어떻게 될까요? 아까 위에 "Default"라는게 있었
죠? Default가 True로 된 항목을 수행하게 됩니다. Default가 True인 항목이
없다면, CGI Request Error 어쩌구 저쩌구 나오게 되죠. 에러를 표시하는 항목
을 따로 하나 만들어 그걸 Default로 두는 것도 좋은 습관이지요. 기왕이면
에러도 이쁘게 보이고 싶으니까..
이벤트는 OnAction 이벤트 하나가 있을뿐인데요.. 그냥 위에서 더블클릭하
면 됩니다. 그러면 커서가 코드창으로 옮겨지고 빈 procedure가 하나 생기
는데요.. 바로 여기서 하고 싶은 내용을 코딩하는겁니다.
실제로 돌아가는 간단한 예제를 하나 만들어보고 여기까지만 하기로 하죠.
커서가 옮겨간 코드창에 아래와 같이 한 줄만 입력해보세요.
Response.Content := 'Hello !!';
그리고 컴파일 한 후 웹브라우저에서 "http://…/babo.exe/abc"를 불러보세요.
Hello !! 라는 글자가 나타나죠?
해당 페이지의 "소스보기"를 해보면 아시지만, 태그 같은건 아무것도 없고
그냥 내용만 있습니다. 원래 그렇게 만들었으니깐… 정식 html 문서를 출
력하려면
Response.Content := '<html><body>Hello !!</body></html>';
처럼 태그까지 모두 포함해서 출력해 줘야 합니다.
다음에는 파라미터 입력받는 방법을 적어보도록 하겠습니다.
언제가 될지는 기약을 못하겠지만 조만간 올리도록 할께요..
제 목:[강좌] 델파이로 만드는 CGI 2 .. Request 관련자료:없음 [660]
보낸이:배한백 (째즈토끼) 2000-02-25 22:21 조회:379
2. Request Class
abc라는 액션을 하나 만들고 OnAction 이벤트 위에서 두번클릭하면 아래와 같
은 구문이 코드창에 나타납니다.
procedure TWebModule1.WebModule1abcAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
end;
여기서 넘어오는 값들을 살표보면요..
● Sender : 아마도 WebModule1 이 넘어오지 않을까 싶지만 알려고 시도해
보지도 않았기 때문에 알수가 없어요. 프로그램내에서도 사용되는일이 아마 없
을듯..
● Request : 사용자의 요청에의한 각종 환경변수들이 이리로 넘어옵니다.
아래에서 자세히 살펴보도록하죠.
● Response : CGI가 만드는 페이지와 각종헤더 등의 반환값을 이리로 넘겨줍
니다.
역시 아래에서…
● Handled : 보통은 건들일이 없는 건데요.
이걸 True로 만들면 "여기서 페이지를 만들고 전송까지 다 했다"라는 말이 됩
니다. 이게 True로 되면 Response.Content 에 무슨 내용이 들어가더라도 날아
가지 않습니다. 보통은 False로 되어 있으니 핸들러가 Response.Content 의
내용을 HTTP Header 와 붙여서 따로 전송하게 되죠.
오늘은 이중에서 Request 에 대해 살펴보도록 하겠습니다.
먼저 프로퍼티부터 보죠..
● Accept : 별루 필요 없어요.
사용자에게는 보이지 않지만 HTTP Header 부분에 Accept-mime 인가 뭔가하
는게 따라가는데 그 값을 가지고 있는 것 같아요.
● Authorization : 역시 필요 없어요.
마찬가지로 HTTP Header에 들어 있는겁니다.
● CacheControl : 캐쉬제어에 관련된건데, 아마도 필요없을 것 같네요.
나중에 Response에서는 캐쉬제어가 필요하겠지만요. 역시 HTTP Header에..
● Connection : 이것두 HTTP Header에 들어 있는 내용으로 필요없을듯.
에유 이거 하나하나 다 쓸려니 장난이 아니네요.
제가 생각하기에 필요없다고 판단되는건 과감히 빼고 알맹이만 골라 설명할께
요.
● ContentFields : POST 방식으로 값이 전달 되었을때 값이 들어있는
TStrings 형 프로퍼티입니다.
A=1
B=2
... 이런식으로 저장이 되어 있겠지요.
A의 값을 얻고 싶다면 Request.ContentFields.Values['A'] 이렇게 하면 됩니다.
● CookieFields : 쿠기값이 파싱되어 들어있는데요..
델파이의 쿠키 제어하는 함수들이 문제가 조금 있는 관계로 자세한건 나중에
설명을 할께요.
● MethodType : 매개변수가 GET 방식으로 전달되었는지 POST 방식으로 들
어 왔는지를 표시합니다. 그 이외에 PUT방식이니 HEAD방식이니 하는것도 있
지만 일반적으로 사용하지 않는 방식입니다.
● QueryFields : 위의 ContentFields가 POST방식으로 전달된 값이 들어있다
고 했는데, 이건 GET방식으로 들어 오는 값이 들어있습니다. 나머지는 같아요.
● RemoteAddr : 요청한 사용자의 IP주소가 들어옵니다. 로그화일 같은걸 CGI
에서 따로 만들고 싶다면 이값을 참조하시구요. 보안과 관련해서 응용할 수도
있습니다. 예를 들어 같은 IP에서 비밀번호를 세번이상 잘못입력했을경우 하루
동안 그 IP Address의 접근을 금지시킨다든지 등등..
● RemoteHost : 위의것이 IP 주소라면 이건 도메인 네임이 들어옵니다. 보통
서버급 컴퓨터가 아니면 도메인네임이 없거나, reverse domain lookup을 지원
해주지 않는 DNS도 있기때문에 일반적으로 위의 IP주소가 그대로 오게됩니다.
● ScriptName : 지금 만드는 이 프로그램의 이름이 들어오게됩니다.
근데 완전하게 들어오지 않고 대충 들어오기 때문에 별로 필요가 없어요.
지금 이게 실행이되기는 되는데 exe가 돌아가는지 dll로 돌아가는지 프로그램
내에서 판단해야할 일이 있을때, ScriptName이 필요한데, 이 프로퍼티로는 판
단이 안되기 때문에, 나중에 따로 만들어서 사용하도록 하겠습니다.
● ServerPort : 웹서버의 포트번호가 들어오는데, 보통은 80번이죠.
대충 사용되는것들만 추려냈는데요,
이제 실전으로 들어가 볼께요.
가장필요한건 ContentFields하고 QueryFields인데요.. Html문서 구성상 GET
방식일 수도 있고 POST방식일 수도 있기때문에 그때 그때 맞는걸 골라써야합
니다.... 라고 말하고 있지만 이건 상당히 번거로운 일이죠. html문서에서 방식
을 바꾸면 프로그램도 뜯어 고쳐야 하니깐..
간단한 편법을 쓰자면요..
WebModule1의 이벤트중에 BeforeDispatch 라는게 있습니다.
이건 본격적으로 CGI작업을 하기전에 할일 처리하는 부분인데요.. 여기다가
if Request.MethodType = mtPost then begin
Request.QueryFields.AddStrings(Request.ContentFields);
end;
라는걸 집어넣어 버립니다.
그리고 이제부터는 GET 방식이든 POST방식이든 신경 쓰지 말고 QueryFields
라는 걸로만 다 처리하면 되는 겁니다. ContentsFields라는게 있다는 것도 잊
어 버리시구요.
GET방식인걸 ContentFields로 통합해도 관계없습니다. QueryFields가 두글자
적어서 키보드 두드리는 횟수 줄일려구 저는 그쪽으로 몰아 넣었어요 (-.-)
GET방식과 POST방식의 차이는 다들 잘 알고 계시겠죠? GET방식은 URL에
파라미터가 붙어서 넘어오는데 255bytes가 한계입니다. (환경변수 길이제한때
문에 그런건가봐요.) 그나마 인코딩된 후의 255bytes여서 조금만 길어지면 잘
려버립니다. (참고로 한글 한글자를 인코딩하면 6bytes) 반면에 POST방식은
표준입력으로 값이 넘어오는데 아마도 길이에 제한이 없을겁니다.
말이 나온김에 WebModule1의 이벤트들을 한번 살펴보고 넘어가죠 (역시 두서
가 없는...)
포로퍼티는 4개 있는데, 몽땅 필요없구요.
● AfterDispatch : 할일 다 하고나서 잠깐 들르는 곳입니다.
모든 항목에 공통적으로 추가할 사항같은게 있다면 여기서 하면 되겠네요.
● BeforeDispatch : 할일을 하기전에 잠깐 들르는 곳입니다.
● OnCreate : 프로그램이 메모리에 로드되면서 실행되는 부분입니다.
exe일경우에는 호출될때마다 실행되지만 dll일 경우 맨 처음 호출될 때 한번만
실행된다는걸 명심..
● OnDestroy : 메모리에서 내려갈때 실행되는 부분입니다.
dll일경우에는 웹서버를 종료시킬때 실행되겠죠.
다시 Request로 돌아가서...
메소드를 살펴봐야하는데, 메소드에는 필요한게 하나도 없군요. 모두 무시. 무
시.
그럼 실제 사용 예를 보도록 하죠.
이런 html문서가 있다고 쳐요.
<body><form action="/cgi-bin/babo.exe/abc" method="post">
<input type="text" name="ID"><br>
<input type="password" name="PASSWD"><br>
<input type="submit" value="로그인">
</form>
</body>
사용자의 아이디와 비밀번호를 묻는 화면인데요, 여기서 사용자가 ID를 "KKK"
로 비밀번호를 "111"로 입력하고 확인 버튼을 눌렀어요.
그러면 델파이에서는
var
ID, PASSWD : String;
begin
ID := Request.QueryFields.Values['ID'];
PASSWD := Request.QueryFields.Values['PASSWD'];
...
end;
이렇게 하면 사용자가 입력한 값이 각각 ID와 PASSWD라는 지역변수로 넘어
가게 됩니다.
post방식인데 왜 QueryFields 로 받냐구요? 음.. BeforeDispatch에 위에서 설
명한 통합루틴이 들어있다고 생각하세요.. 저는 그렇게하는게 습관이 되나서 앞
으로도 계속 이렇게 될거에요.
이런것들은 문자형이니깐 상관이 없는데, 숫자형을 입력받을때는 고려해야할일
이 많아요.
원래 파스칼이란 언어가 교육용으로 시작된 이유로해서 왠만한 에러처리를 프
로그래머에게 맡기지 않고 지가 알아서 처리합니다. 저처럼 C를 사용하던 사람
들에게는 엄청 짜증스러운 부분이죠.
보통 숫자형을 입력받을때는
var
Age : Integer;
begin
Age := StrToInt(Request.QueryFields.Values['AGE']);
...
라고 쓰게되는데 이건 절대로 안될일입니다.
똑똑한 사용자만 홈페이지에 접속한다면 문제가 안돼겟지만 가끔은 저처럼 나
이를 하라는데 이름을 쓰거나 "몰라"등으로 쓰는 사람이 있을수도 있기때문입니
다.
만약 사용자가 빈칸이나 문자를 집어넣어버린다면 위와같이 코딩했을경우 CGI
Request Error : EConvertError 어쩌구저쩌구가 튀어나옵니다.
따라서 모든 예외처리를 프로그램이 직접담당하게 만들어야합니다.
참고로 제가 사용하는 문자열->숫자 변환함수 입니다.
function atoi(STR : String) : Integer;
var
BUF : String;
I,LEN : Integer;
begin
LEN := Length(STR);
if LEN=0 then atoi:=0
else if (Str[1]<>'-') and
((Ord(Str[1])<Ord('0')) or (Ord(Str[1])>Ord('9'))) then Result:=0
else begin
BUF := STR[1];
If LEN>1 then
For I:=2 to LEN do begin
If ((Ord(Str[I])<Ord('0')) or (Ord(Str[I])>Ord('9'))) then break;
BUF := BUF + STR[I];
end;
If BUF='-'
then Result := 0
else Try Result := StrToInt(BUF)
Except Result := 0
end;
end;
end;
이건 문자열등을 입력하면 0으로 바꾸고 에러는 나오지 얺습니다.
C의 atoi 함수와 똑같이 만들었어요.
123abc -> 123 이렇게 변환되도록요.
이런식으로 모든 예외에러를 직접 처리하는 방안을 만들어야합니다.
이렇게 해놓고 나이가 0 이라면 "장난치지 마세요"등의 에러메세지 출력 페이지
를 보여주면 되겠죠.
음.. 다음에는 Response 클래스를 살펴보죠.
그럼.
제 목:[강좌] 델파이로 만드는 CGI 3 .. Response 관련자료:없음 [661]
보낸이:배한백 (째즈토끼) 2000-02-25 22:21 조회:316
3. Response Class
Request가 사용자의 요청을 받는 놈이라면 Response는 사용자에게 결과를
보내는 놈입니다.
필요한 프로퍼티를 살펴보지요.
● Content : 사용자에게 보여줄 html문서를 요기다가 넣습니다.
● ContentStream : 역시 사용자에게 보내는 반환값인데, 이건 스트림 형식입
니다. 스트림 형식이 뭐냐구요? 저두 몰라요.(-.-). 이미지 카운터 같은걸 만들
려면 html형식이 아닌 gif형식을 보내야 하는데, 아마 그때 유용하게 쓸 수 있
을겁니다.
Content 와 ContentStream 둘 다 세팅 할 경우 ContentStream에게 우선권이
있다고 하는군요.
● ContentType : 보통은 text/html을 쓰지만, 이미지카운터 등 출력형식이
html이 아닐 경우에는 따로 세팅해야 합니다.
● Cookies : 쿠키를 심을때 쓰라고 만들어 놓은 것 같은데... 이거 쓰지 마세
요. 제대로 안돼요.
● CustomHeaders : html문서내부에 들어가는 것이 아니고 HTTP Header에
들어가는 부분입니다.
아마도 다음에 쿠키편에서 등장할거에요.
● Expires : 아마도 캐쉬 유효기간을 설정하라고 만든 것 같은데, 역시 제대로
안됩니다.
도움말에는 GMT형식으로 날아간다고 친절하게 설명되어 있지만 절대로 GMT
표준형식으로 안날아갑니다. 속지마세요. 캐쉬제어하는것도 다음에 따로 다루겠
습니다.
대충 이 정도만 알면 돼겠네요. 쓰고보니 별것도 없군요.
여기서 시간 체계에 대해서 짚고 넘어가도록 하죠.
HTTP 프로토콜에서는 GMT 표준시간 체계를 사용하는데요.
표시형식은 이렇습니다.
Sun, 25 Mar 2000 18:32:00 GMT
요일, 일 월 년 시:분:초 GMT
뒤에 GMT가 없으면 로컬 시간이고 GMT가 붙으면 그리니치 천문대의 시간이
죠.
세계가 같은 시간을 써야 혼란이 없기 때문에 항상 GMT가 붙은 GMT 시간을
써야 합니다.
GMT시간을 사용하기 위해서는 파스칼에서 제공하는 시간관련 함수들을 사용
하기가 어렵습니다.
VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);
VOID GetLocalTime(LPSYSTEMTIME lpSystemTime);
라는 윈도우 API 함수를 이용해야 하는 데, GetSystemTime은 전세계 어느 나
라에서 실행하더라도 같은 값이 나오고, GetLocalTime은 나라별로 시차가 고려
된 시간이 나옵니다. 그러면 우리는 뭘 써야 할까요? 당연히 GetSystemTime
을 사용해야 합니다. 파스칼의 Now 함수를 사용하면 local time이 나오기 때문
에 사용할 수 없어요. (시차를 계산하면 될걸 왜 어려운 API함수를 사용 하냐구
요? 음.. 혹시 압니까.. 여러분이 만든 프로그램을 다른 나라 사람이 받아서 사
용할지..)
저같은 경우는 C에서 사용하는 시간체계에 익숙해놔서 모든 시간을 C형식으
로 바꿔 사용합니다.
이상하게도 파스칼의 TDateTime이 연산이 잘 안되더라구요. 물론 제가 멍청해
서 그렇겠지요.
C는 파스칼처럼 Double형이 아닌 LongInt형의 시간을 씁니다. 1970년 1월 1
일 0시 0분 0초부터 1초경과시마다 1씩 더해가는 시간이죠. 아마도 컴의
ROM-BIOS 타이머도 이렇게 동작할 겁니다.
어떤식으로 계산하든 결과가 위의 GMT표준 시간체계 표시형식으로 출력만 돼
면 관계없습니다.
참고로 제가 사용하는 계산 루틴의 소스를 첨부할께요.
소스에 등장하는 시간형식은 세가지인데요.
각각 C형식, 파스칼형식, 시스템 형식으로 부르기로 하겠습니다.
앞에붙은 UTC_ 은 그냥 그룹지놓을려구 폼으로 붙여놨어요.
//----------------------------------------------------------
// 이건 선언부분 이구요.
Function UTC_GetTimeGMT : LongInt;
Function UTC_GetTimeLOCAL : LongInt;
Function UTC_TimeToStr(TIMER : LongInt) : String;
Function UTC_TimeToStrGMT(TIMER : LongInt) : String;
Function UTC_SystemToUTC(timebuf : _SYSTEMTIME) : LongInt;
Procedure UTC_UTCToSystem(utctime : LongInt; var timebuf :
_SYSTEMTIME);
Function UTC_DateTimeToUTC(tm : TDateTime) : LongInt;
//----------------------------------------------------------
// 국제표준시간을 얻어옵니다.
// 리턴형식은 C의 시간 형식입니다.
Function UTC_GetTimeGMT : LongInt;
var
timebuf : _SYSTEMTIME;
begin
GetSystemTime(timebuf);
Result := UTC_SystemToUTC(timebuf);
end;
// 시스템형식의 시간을 C의 시간형식으로 변환합니다.
// 리턴형식은 C의 시간 형식입니다.
Function UTC_SystemToUTC(timebuf : _SYSTEMTIME) : LongInt;
const
julian_days: Array[1..12] of LongInt
= ( 0, 31, 59, 90, 120, 151,
181, 212, 243, 273, 304, 334 );
var
elapsed_days : LongInt;
function _DayOfTwoYear(FromY, ToY : Integer) : LongInt;
label _JP;
var
I : Integer;
begin
Result := (ToY - FromY + 1) * 365;
I := ((FromY div 4)+1) * 4;
while I<=ToY do begin
if (I and 3)<>0 then goto _JP;
if ((I mod 100)<>0) or ((I mod 400)=0) then Inc(Result);
_JP:
I:=I+4;
end;
end;
begin
elapsed_days := _DayOfTwoYear(1970, timebuf.wYear-1) +
julian_days[timebuf.wMonth] +
timebuf.wDay - 1;
if isLunar(timebuf.wYear) and (timebuf.wMonth > 2)
then Inc(elapsed_days);
Result := elapsed_days * 86400 +
timebuf.wHour * 3600 +
timebuf.wMinute * 60 +
timebuf.wSecond;
end;
// 현지시간을 얻어옵니다.
// 리턴형식은 C의 시간 형식입니다.
Function UTC_GetTimeLOCAL : LongInt;
var
timebuf : _SYSTEMTIME;
begin
GetLocalTime(timebuf);
Result := UTC_SystemToUTC(timebuf);
end;
// C의 시간형식을 시스템 형식으로 변환합니다.
Procedure UTC_UTCToSystem(utctime : LongInt; var timebuf :
_SYSTEMTIME);
const
julian_days: Array[1..12] of word
= ( 31, 99, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 );
var
Max : Integer;
Aday, ATime : LongInt;
begin
ADay := utctime div 86400;
ATime := utctime mod 86400;
timebuf.wDayOfWeek := ( ADay + 4 ) mod 7;
timebuf.wYear := 1970;
repeat
if isLunar(timebuf.wYear) then Max:=366 else Max:=365;
if ADay<Max then Break;
Aday := Aday - Max;
Inc(timebuf.wYear);
until False;
if isLunar(timebuf.wYear) then julian_days[2]:=29 else julian_days[2]:=28;
timebuf.wMonth := 1;
repeat
if ADay<julian_days[timebuf.wMonth] then Break;
Aday := Aday - julian_days[timebuf.wMonth];
Inc(timebuf.wMonth);
until False;
timebuf.wDay := Aday + 1;
timebuf.wHour := ATime div 3600;
ATime := ATime mod 3600;
timebuf.wMinute := ATime div 60;
timebuf.wSecond := ATime mod 60;
timebuf.wMilliseconds := 0;
end;
// 파스칼 형식을 C의 형식으로 변환합니다.
Function UTC_DateTimeToUTC(tm : TDateTime) : LongInt;
var
SYSBUF : SYSTEMTIME;
begin
DecodeDate(tm, SYSBUF.wYear, SYSBUF.wMonth, SYSBUF.wDay);
DecodeTime(tm, SYSBUF.wHour, SYSBUF.wMinute, SYSBUF.wSecond,
SYSBUF.wMilliSeconds);
Result := UTC_SystemToUTC(SYSBUF);
end;
// C의 시간형식을 표준 문자열로 리턴합니다.
Function UTC_TimeToStr(TIMER : LongInt) : String;
const
wday : Array[0..6] of String[3]
= ( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' );
mon : Array[1..12] of String[3]
= ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
var
timebuf : _SYSTEMTIME;
begin
UTC_UTCToSystem(TIMER, timebuf);
Result := format('%s, %s %s %s %s:%s:%s',
[ wday[timebuf.wDayOfWeek],
itoa(timebuf.wDay, 2),
mon[timebuf.wMonth],
itoa(timebuf.wYear, 4),
itoa(timebuf.wHour, 2),
itoa(timebuf.wMinute, 2),
itoa(timebuf.wSecond, 2) ]);
end;
// 표준문자열 뒤에 GMT를 붙여 리턴합니다.
Function UTC_TimeToStrGMT(TIMER : LongInt) : String;
begin
Result := UTC_TimeToStr(TIMER) + ' GMT';
end;
//----------------------------------------------------------
실제로 시간이 사용되는 곳을 별루 많지 않아요.
캐쉬제어할때 얼마나 오랫동안 캐쉬할 것인지 하는 부분과 쿠키를 심을때 쿠키
의 유효기간을 설정하는 부분 등 몇개 안됩니다. 하지만 필요없다고는 할 수 없
겠죠?
쿠키의 유효기간을 30일로 설정하고 싶다면 쿠키의 유효기간 들어가는 부분을
아래와 같이 합니다.
30일은 (3600*24*30)초니깐..
'expires="'+UTC_TimeToStrGMT(UTC_GetTimeGMT+(3600*24*30))+''";'
소스에 보면 itoa 같은 제가 만든 함수들이 들어 있는데요.. 이건 강좌가 끝난
후 문서하고 같이 묶어서 자료실에 올리도록 할께요. 지금은 그냥 이해만 하세
요.
계속 원론적인 이야기만 하면 재미 없으니깐 다음엔 이미지 카운터만들기 실제
예를 한번 해볼께요.
그럼,,
제 목:[강좌] 델파이로 만드는 CGI 4 .. 이미지 카운터 예제 관련자료:없음 [662]
보낸이:배한백 (째즈토끼) 2000-03-19 16:37 조회:239
4. 이미지 카운터
흑..
괜히 이미지 카운터를 예제로 잡았네요. 이걸 만들려니 엄청나게 복잡하군요.
예전에 만들어 놓은 게 있어 간단히 해결될 줄 알았는데… 암튼 예고는 해 놨
는데 안 할 수도 없고…
어쨌든 시작하자구요.
카운터를 만들려면 먼저 GIF 파일의 포멧부터 알아야 합니다. (역시 강좌 주제
에 빗나가는…) 간단히 설명 할께요. GIF파일에는 두 가지 종류가 있습니다.
Gif87a멧과 Gif89a 포멧인데요.
Gif87a 87년도부터 생긴듯한 아주 간단한 그냥 Gif 파일이구요. 89a에는 여러
가지 기능이 추가되었습니다. 바탕이 투명해지는 기능, 레이어를 여러 개 두어
표시에 시간차를 주는 기능(이게 Moving Gif라 불리는 거에요) 등등..
일단 카운터 그림을 만들기 위해서는 0.gif 부터 9.gif 까지 10장의 gif파일이
필요합니다. 이건 87a 포멧이어야 합니다.
그리고 CGI에서는 필요한 낱장을 읽어와서 카운터의 숫자대로 단순히 배열만
하는 겁니다. 그림을 섞을 필요도 없어요. 각각의 레이어로 만들어 좌표만 바
꿔가며 시간차 없이 뿌려주면 됩니다. 물론 출력형태는 89a 형식이 됩니다.
GIF포멧에 관한 자세한 내용은 소프트웨어 동호회(go soft)의 통합강좌란에서
"lt gif" 하면 신문섭님의 강좌가 있으니 참고하시기 바래요..
먼저 그림을 조합하는 부분입니다.
// 이건 액션 함수가 아니라 제가 추가한 함수입니다.
// 나중에 카운터 서비스 액션 함수에서 이걸 불러 쓰죠.
function TWebModule1.CounterCombination(Count, Digit:integer; Dir:String;
var GIF:PChar) : LongInt;
// Count : 표시할 카운터 숫자
// Digit : 몇 자리에 걸쳐 출력할 것인지?
// Dir : 0.gif…9.gif 파일이 들어 있는 디렉토리
// GIF : 조합된 GIF를 돌려 주는곳
// Result : 만들어진 GIF 이미지의 사이즈를 리턴
label Repeated_Block;
var
F : File;
I, K : Integer;
Len : LongInt;
CountIndex : Array[0..40] of Char;
Buf : String;
PBuf : Array[0..1024] of Char;
TempC : Char;
TempB : Byte;
Width_Sum : SmallInt;
g_Header : Array[0..6] of Char;
g_Width, g_Height : Word;
g_BgColor, g_Ratio : Char;
g_BitFlag : Byte;
m_ColorSize : Integer;
i_Flag : Char;
i_BitFlag : Byte;
i_Top, i_Left, i_Width, i_Height : Word;
LZW_Flag : Char;
d_Size : Byte;
begin
if Digit>16 then Digit := 16; // 최대 자릿수를 16자리로.. 이것도 많네..
Buf := itoa(Count, Digit); // 카운터의 수를 문자열로 바꿔 "00015" 처럼
StrPCopy(CountIndex, Buf); // 카운터 문자열을 포인터형으로 변환
Digit := StrLen(CountIndex); // 실제로 몇 자리수인지 다시 계산
Width_Sum := 0; // 그림의 너비 초기화
m_ColorSize := 0; // 그림의 색상 수 초기화
Len := 0; // 그림 저장 공간 포인터 위치 초기화
FileMode := 0; // 읽기 전용 모드
for I:=0 to Digit-1 do begin
AssignFile(F, Dir + CountIndex[I] + '.gif');
{$I-}
Reset(F, 1); // 낱장 GIF 파일을 연다.
if IOResult<>0 then begin
Result := 0;
Exit;
end;
BlockRead(F, g_Header, 6); // 헤더 6바이트 읽고
if (g_Header[0]<>'G') or (g_Header[1]<>'I') or (g_Header[2]<>'F')
then begin // GIF 파일이 아니다.
StrPCopy(GIF, Dir + CountIndex[I] + '.gif<br><br>'
+ '위 화일이 GIF 화일이 맞는지 확인하세요.');
Result := StrLen(GIF);
Exit;
end;
BlockRead(F, g_Width , 2); // 낱장의 너비
BlockRead(F, g_Height , 2); // 낱장의 높이
BlockRead(F, g_BitFlag , 1); // 비트 플래그
BlockRead(F, g_BgColor , 1); // 배경색
BlockRead(F, g_Ratio , 1); // 비율 (?)
if I=0 then begin // 첫번째 그림이면 헤더를 만든다.
StrPCopy(GIF, 'GIF89a'); // GIF 헤더 (레이어의 헤더가 아니다)
{ g_Width는 나중에 기록 }
GIF[8] := Chr(g_Height mod 256); // 높이 high 부분 기록
GIF[9] := Chr(g_Height div 256); // 높이 low 부분 기록
GIF[10]:= Chr(g_BitFlag and $7f); // 비트플래그
GIF[11]:= g_BgColor; // 배경색
GIF[12]:= g_Ratio; // 비율(?)
Len := 13; // 기록 포인터 이동
end;
if (g_BitFlag and $80)=$80 then begin // 계산법은 gif 포멧을 참고
m_ColorSize := (1 shl ((g_BitFlag and $07)+1))*3;
BlockRead(F, PBuf, m_ColorSize);
end;
Repeated_Block :
BlockRead(F, i_Flag, 1);
if IOResult<>0 then i_Flag := Chr($3b); // 낱장그림이 비정상이면
if Ord(i_Flag)=$2c then begin // Image Block 시작
BlockRead(F, i_Left, 2); // 왼쪽 좌표
i_Left := i_Left + Width_Sum; // 조합된 그림의 왼쪽좌표 재계산
BlockRead(F, i_Top, 2); // 위쪽좌표
BlockRead(F, i_Width, 2); // 너비
BlockRead(F, i_Height, 2); // 높이
BlockRead(F, i_BitFlag, 1); // 비트플래그
GIF[Len] := i_Flag; Inc(Len);
GIF[Len] := Chr(i_Left mod 256); Inc(Len); // 레이어의
GIF[Len] := Chr(i_Left div 256); Inc(Len); // 좌표를 기록
GIF[Len] := Chr(i_Top mod 256); Inc(Len);
GIF[Len] := Chr(i_Top div 256); Inc(Len);
GIF[Len] := Chr(i_Width mod 256); Inc(Len);
GIF[Len] := Chr(i_Width div 256); Inc(Len);
GIF[Len] := Chr(i_Height mod 256); Inc(Len);
GIF[Len] := Chr(i_Height div 256); Inc(Len);
if (i_BitFlag and $80)=$80 then begin
m_ColorSize := (1 shl ((i_BitFlag and $07)+1))*3;
BlockRead(F, PBuf, m_ColorSize);
GIF[Len] := Chr(i_BitFlag);
end
else begin
TempB := Ord(i_BitFlag) and $08;
TempB := TempB or (Ord(g_BitFlag) and $87);
GIF[Len] := Chr(TempB or (Ord(i_BitFlag) and $78));
end;
Inc(Len);
for K:=0 to m_ColorSize-1 do begin // 팔레트(팰릿?) 복사
GIF[Len] := PBuf[K];
Inc(Len);
end;
BlockRead(F, LZW_Flag, 1);
GIF[Len] := LZW_Flag;
Inc(Len);
While True do begin
BlockRead(F, d_Size, 1);
GIF[Len] := Chr(d_Size);
Inc(Len);
if d_Size=0 then Break;
for K:=0 to d_Size-1 do begin
BlockRead(F, TempC, 1);
GIF[Len] := TempC;
Inc(Len);
end;
end;
end // Image Block 끝
else if Ord(i_Flag)=$3b then begin
Width_Sum := Width_Sum + g_Width;
CloseFile(F);
Continue;
end
else begin
StrPCopy(GIF, '이미지 화일에서 알 수 없는 인덱스가 발견되었습니
다.<br>'
+ '가능하다면 GIF 87a 버젼으로 만들어 주세요.');
Result := StrLen(GIF);
Exit;
end;
goto Repeated_Block;
{$I+}
end;
GIF[6] := Chr(Width_Sum mod 256); // 조합된 그림의 너비 기록
GIF[7] := Chr(Width_Sum div 256);
GIF[Len] := Chr($3b);
Inc(Len);
Result := Len;
end;
대충 이렇습니다. 만든지가 한참 되고 만들 당시 강좌와 다른 언어로 된 소스
등을 참고해서 겨우 만들어 놓은거라 지금 들여다보니 제가 왜 그렇게 만들어
놓았는지 저조차도 이해가 안되는 부분이 많네요. 그렇다고 또 gif 포멧 하나하
나 뜯어보기도 귀찮고 해서…
대충 설명을 드리면요…
첫번째 낱장의 헤더정보를 만들 그림의 헤더정보로 사용합니다. 물론 폭이나
변동사항은 달라지고요. 각각의 낱장그림은 높이가 모두 같아야 합니다. 폭은
달라도 됩니다. 계산해서 나중에 따로 기록하니깐요.
그리고 각 레이어의 시작 왼쪽좌표를 오른쪽으로 옮겨가면서 배열 하는 겁니다.
이미지만 조합해내면 그 다음은 아무것도 아니죠.
// 실제 서비스 부분입니다. 프로그램이 babo.exe 라면
// <img src="/cgi-bin/babo.exe/count?file=xxx&digit=5"> 형식이 되겠네요.
procedure TWebModule1.ActionCount(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Count, Size : LongInt;
Digit : Integer;
CountFile, ImageDir, IPAddr : String;
GIF : PChar;
F : TextFile;
Fd : File;
Buf : String;
stCount: TMemoryStream;
begin
CountFile := Request.QueryFields.Values['file'];
// 카운터가 기록되는 파일, 하나의 프로그램으로 여러 개의 카운터를
// 돌릴 수 있게 하기 위해서
if CountFile='' then CountFile:='main';
Buf := Request.QueryFields.Values['digit']; // 몇 자리에 걸쳐 표시?
Digit := atoi(Buf);
if Digit=0 then Digit:=5;
ImageDir := Request.QueryFields.Values['imagedir'];
// 이미지 디렉토리도 파라미터로 넣을 수 있다.
// 이미지 디렉토리가 지정되지 않으면 디폴트로 "Images\"
if ImageDir='' then ImageDir:='Images\'
else if ImageDir[Length(ImageDir)]<>'\' then ImageDir := ImageDir+'\';
GIF := AllocMem(Digit*2048); // 그림을 위한 메모리 공간 할당
if GIF=nil then begin
Response.Content := '<P> 이미지 화일을 조합하기 위한 메모리가 부족
합니다.';
Exit;
end;
{$I-}
AssignFile(F, CountDir+CountFile+'.count');
IOResult;
FileMode := 0;
Reset(F);
if IOResult<>0 then Count:=0
else begin
// 카운터 뒤에 최종 접속자의 IP 주소가 기록되어있다.
ReadLn(F, Count, IPAddr);
CloseFile(F);
end;
// 최종 접속자가 현재 접속자가 아니면 카운터 증가
if Pos('#', IPAddr)<>0 then Delete(IPAddr, 1, Pos('#', IPAddr));
if IPAddr<>Request.RemoteAddr then begin
FileMode := 1;
Inc(Count);
Rewrite(F);
if IOResult=0 then begin
WriteLn(F, Count, ' #', Request.RemoteAddr);
CloseFile(F);
end;
end;
{$I+}
// 그림을 조합한다.
// CountDir 은 전역 상수 또는 변수로 선언되어 있다고 가정..
Size := CounterCombination(Count, Digit, CountDir+ImageDir, GIF);
if Size=0 then begin
FreeMem(GIF);
Response.Content := '<P> 이미지 화일의 디렉토리 지정이 제대로
되었는지 확인하세요.';
Exit;
end;
// 그림을 메모리 스트림 형식으로 집어 넣는다.
stCount := TMemoryStream.Create;
stCount.SetSize(Size+1);
stCount.Position := 0;
stCount.Write(GIF^, Size);
stCount.Size := Size;
stCount.Position := 0;
// ContentType을 수동으로 설정해야한다.
// Default는 text/html 이기 때문에 바꿔야 한다.
Response.ContentType := 'image/gif';
// 캐쉬를 30분동안만 시킨다.
Response.SetCustomHeader('Expires',
UTC_TimeToStrGMT(UTC_GetTimeGMT+(30*60)));
Response.ContentLength := stCount.Size;
Response.SendResponse; // 헤더 정보를 직접 날린다.
Response.SendStream(stCount); // 그림을 직접 날린다.
stCount.Free; // 메모리 스트림 해제.
FreeMem(GIF); // 그림공간 해제
Handled := True; // 페이지를 벌써 전송했다는 걸 알림
end;
원래 이게 여러가지 쓸데 없는 기능들이 많은 카운터인데, 이해를 돕기 위해
여러가지를 뺐습니다. 워드로 옮겨 와서 지우다 보니 아마 덜 지우거나 더 지
운 부분이 있을지도 모릅니다.
또, 여기서 낱장그림의 디폴트 디렉토리를 "images\"라고 했는데, 이건 CGI
일 경우와 ISAPI일 경우가 서로 다른 결과를 가져옵니다. 두 개의 시작 디렉토
리 위치가 다른 까닭이죠. 그래서 CountDir이란 전역 변수를 앞에 붙여서 적
었습니다.
쉬운 예제를 올리려고 했던 의도와는 달리 상당히 복잡한 게 올라가게 되었네
요. T.T
앞으로는 다음 강좌 예고를 안 해야겠습니다.
그럼.
제 목:[강좌] 델파이로 만드는 CGI 5 .. 쿠키 관련자료:없음 [663]
보낸이:배한백 (째즈토끼) 2000-03-19 16:37 조회:288
^^;
아주 오랜만에 쓰는 글인데요..
아래 4번은 1,2,3 올라갈때 적어 뒀던건데 이제서야 올라가네요.
내용이 이해하기가 많이 어려우니깐, 이미지 카운터 부분은 건너뛰셔도 됩니다.
혹시 나중에 필요한 일이 있으면 참고 하시구요.
요즘 워낙 일이 많아 놔서 시간이 잘 안나네요.. 그래도 틈틈이 올리도록 할께
요..
이번 강좌에서는 쿠키에 대해서 적어보겠습니다.
5. Cookie
델파이에서는 왜 그런지 쿠키가 엉망입니다.
얻어오는건 그런대로 되는데, 심는게 제대로 안됩니다.
요즘 버전은 어떤지 모르겠지만 델파이 4의 Update Pack #1 에서 까지는 분명
엉망이었습니다. 그래서 저는 쿠키 처리하는 부분을 따로 만들어서 사용하고
있는데요.. 제가 만든 쿠키 루틴은
Procedure SetMyCookie(Response:TWebResponse; Name:String;
CookieList:TStrings; AExpires :LongInt); overload;
Procedure SetMyCookie(Response:TWebResponse; Name:String;
CookieList:TStrings; Path: String; AExpires :LongInt); overload;
Procedure GetMyCookie(Request:TWebRequest; Name:String;
ResultList:TStrings);
요렇게 두 가지(세 가지?) 함수입니다.
● SetMyCookie : 쿠키를 세팅하는 함수입니다.
Response : 액션함수의 Response를 넣어 줍니다.
Name : 쿠키의 이름입니다.
CookieList : 실제 쿠키들이 들어있습니다.
Path : 쿠키 유효 경로입니다.
생략하면 홈페이지 전체..
Aexpires : 쿠키 만료 날짜입니다.
현재 세션에만 유효하도록 하려면 ?1을..
● GetMyCookie : 쿠키를 얻어오는 함수입니다.
Request : 액션함수의 Request를 넣어줍니다.
Name : 쿠키의 이름입니다.
ResultList : 쿠키가 저장되는 곳입니다.
아주 간단한 것 같지만 사실은 조금 복잡합니다.
Name은 실제 쿠키이름으로 쓰이고, 분류명의 개념입니다.
예를 들어 사용자의 정보를 쿠키에 담아 놓는다면
NAME : Profile
CookieList :
이름=홍길동
전화=없음
주소=모름
취미=훔쳐보기
…
이게 하나의 쿠키가 되는 것입니다.
또 다른 쿠키를 동시에 써야 한다면 그건 Path를 바꿔야 합니다.
즉,
NAME : ShoppingCart
CookieList :
구입37=1
구입42=2
구입156=1
…
이건 Path 를 '/' 또는 '/cgi-bin' 등의 Path로 보내야 한다는 것입니다.
짧은 영어실력으로 Cookie Spec.을 보니깐 같은 Path에도 두 개의 Cookie를
세팅할 수 있다고 되어있는 것 같았는데, 이상하게도 익스플로러와 넷스케이프
둘 다 지원이 안됩니다.
세부적인 사항은 [부록]으로 라이브러리 소스를 자료실에 올리도록 할께요.
역시 글 실력이 짧으니 소스로 때우는 방법밖에 없는 것 같아요 (--;)
아마도 여러분은 그걸 그대로 사용하시면 될겁니다.
입맛에 맞게 고쳐도 좋구요.
받아 보시고 궁금한 점은 아무때나 믈어보세요.
그럼 다음에..
제 목:[강좌] 델파이로 만드는 CGI 6 .. 웹로봇 관련자료:없음 [665]
보낸이:배한백 (째즈토끼) 2000-03-29 17:18 조회:219
안녕하세요.
이번에는 웹로봇에 대해서 알아보기로 하겠습니다.
웹로봇이 뭔지는 다들 아시죠?
혼자서 다른 홈페이지(또는 telnet등)로 CGI가 직접 접속을 해서 그 내용을 가
져와서 그 자료를 가공하는 기능을 가진 프로그램을 말합니다.
알타비스타라는 검색엔진이 있죠? 알타비스타는 검색엔진 외에 웹로봇이 따로
계속해서 돌아가고 있습니다. 검색엔진은 자료를 찾아서 사용자에게 보여주는
역할을 하는데, 그 자료는 누가 모았을까요? 바로 웹로봇입니다. 초기에 어떤
홈페이지를 몇 개만 등록해놓으면 그 홈페이지의 내용을 가져와서 DB에 담고
그 페이지를 파싱한 후 하이퍼링크만 뽑아내서 스택에 저장하고 그 스택을 찾
아다니며 계속 똑같은 일을 반복하는 겁?수 없는 컴포넌트입니
다. 그래서 어쩔 수 없이 Windows API의 winsock 함수를 사용해서 직접 TCP/IP
접속을 해야 합니다만 제가 자료실에 올린 강좌관련 자료에 보면 WebRobot.pas
가 포함되어 있어요. 여러분은 그냥 이걸 불러서 사용하시면 되겠습니다. 포함
된 함수들을 각각 설명을 드릴께요.
● Function HTTPRequest(URL:String; MethodType:TMethodType; Parameter:String)
: String; overload;
● Function HTTPRequest(URL:String; MethodType:TMethodType; Parameter:String;
적어주면 그 코드가
넘어올경우 이쪽에서 접속을 끊어버립니다. 대소문자는 구별하지 않
습니다. 보통 "</body>" 또는 "</html>"을 적어주면 되겠죠. 제 경험에
의하면 오라클 웹서버 몇몇 버전이외에는 필요가 없었습니다.
Cookie : 이건 제목 그대로 쿠키입니다. 그쪽에서 날아오는 쿠키값을 미리 받아
서 가지고 있어야 보내는 것도 가능하겠죠?
● Function HTTPRequestExt(URL:String; MethodType:TMethodType;
Parameter:String) : String;
이건 같은 기능을 하는 함수지만 외부의 어떤 프로그램을 실행시키고 그 프로
그램이 긁어와서 다시 여기로 전달해주는 방식을 사용합니다. 그 외부프로그램
은 제가 넣지 않았구요, 아마 이렇게까지 할 필요는 없을 것 같습니다.
모든 함수의 리턴값은 그 홈페이지의 내용입니다.
리턴값중에 "(=ErROr*)"라는 문자열이 포함되어있다면 그건 접속이 실패한 경
우의 에러입니다. 그 외의 에러(예를 들면 404 File Not Found 등등)들은 직접 감
지해야 합니다.
간단한 사용예를 하나 들어볼께요.
PostData := 'ID=' + MyID + '&'
+ 'PASSWD=' + MyPasswd ;
HTTPResponse := HTTPRequest('http://www.xxxx.com:8080/cgi-bin/login',
mtPost, PostData);
if Pos('(=ErROr*)', HTTPResponse)<>0 then Exit;
기타 파싱 및 가져온 데이터 처리는 직접 알아서 하셔야 합니다.
오늘은 또 여기까지…
제 목:[강좌] 델파이로 만드는 CGI 7 .. 메일보내기 관련자료:없음 [666]
보낸이:배한백 (째즈토끼) 2000-03-29 17:19 조회:341
안녕하세요.
이번에는 폼메일에 대해 알아보기로 하겠습니다.
폼메일이란 것은 사용자가 웹브라우저상에서 입력한 내용을 관리자의 메일함으
로 일정 양식에 맞추어져 날아오는것입니다.
이걸 구현하기 위해서는 먼저 CGI프로그램이 전자우편을 발송하는 기능을 내
장해야 합니다. 그리고 정해진 양식의 포멧을 알아야 하겠죠. 예를들어 사용자
가 홈페이지에서 이력서의 내용을 입력하면 엑셀로 만들어진 이력서 파일이 관
리자의 편지함으로 배달이 되게 만들려면, 여러분의 엑셀의 데이터화일 (*.xls)의
포멧을 알아야합니다.
이렇게 워드나 엑셀의 양식으로 방유하지 않고 직접 메일을 전송하게 구현할 수 있
지만 이 컴포넌트를 이용한다면 SMTP서버가 필요하게 됩니다.
그럼 NMSMTP 의 포로퍼티와 메소드, 이벤트 들에 대해서 살펴보겠습니다.
<프로퍼티>
● ClearParams : 메일을 보낸 후 PostMessage의 내용을 모두 삭제할 것인지 말
것인지..
● EncodeType : 매일의 인코딩 형식을 정하는 건데, 저도 자세히는 모르겠어요.
한글이 깨져서 날아가거나 html 메일이 text처럼 날아갈 때 이걸 조정해 보세요.
● FinalHeader : 메일이 날아갈 때마다 꼬리표처럼 여기에 적힌 글이 내용에 추
가되어 따라갑니다. 별로 쓸모는 없을 듯..
● Host : SMTP 서버의 주소를 적어줍니다. (*)
● Port : SMTP 서버의 서비스 포트를 적어줍니다. 일반적으로 SMTP는 25번
포트를 이용합니다. (*)
● UserID : SMTP서버의 사용권한이 있는 사용자의 ID를 적어줍니다. (*)
● SubType : Mime-type을 설정합니다. Html 편지인지 Text 편지인지 등등
● TimeOut : 밀리세컨드 단위의 타임아웃을 설정합니다. 여기 적힌 시간내에
SMTP 서버로 접속이 안되면 포기합니다.
● PostMessage : 메일관련 사항이 여기에 다 있습니다. (*)
○ Attachments : 첨부화일이죠. 파일의 풀패스를 적어줍니다.
○ Body : 편지의 본문입니다.
○ FromAddress : 보내는 사람의 전자우편 주소
○ FromName : 보내는 사람의 이름
○ ReplyTo : 보내는 사람의 답장 받을 주소
○ Subject : 제목
○ ToAddress : 받을 사람들의 전자우편 주소들
○ ToCarbonCopy, ToBlindCarbon : 참조인의 주소들
<메소드>
● Abort : 편지 보내기를 취소합니다. 이벤트 핸들러에서 사용.
● ClearParameters : PostMessage를 초기화 합니다.
● Connect : SMTP 서버로 접속해라.
● SendMail : 편지를 보내라.
● Disconnect : 접속을 끊어라.
<이벤트>
● OnAttachmentNotFound : 첨부한 파일을 찾을 수 없을 때
● OnAuthenticationFailed : SMTP 가 사용자ID를 찾을 수 없다며 거부할 때
● OnConnect : SMTP에 접속이 성공했을 때
● OnConnectionFail : SMTP에 접속이 실패했을 때
● OnConnectionRequired : 접속도 안하고 메일을 보내려 할 때
● OnDisconnect : 접속이 종료되었을 때
● OnEncodeStart, OnEncodeEnd : 인코딩 작업이 시작하기전과 끝난 후에
● OnFailure : 메일 보내기가 실패했을때
…
에구 이거 별루 쓸데도 없는 것들이 무지 많군요. 뭐 이름만 봐도 대충 언제 일
어나는 이벤트인지 눈치채기 어렵지 않으니 이만 줄이도록 할께요.
CGI/ISAPI 에서는 디버깅이 어렵기 때문에 이 컴포넌트의 테스트는 일반 API상
에서 해보는 것이 좋습니다. 거기서 사용하는 루틴을 그대로 CGI상에서도 사용
할 수 있으니깐요.
사용법은 아주 간단하니 굳이 복잡한 예는 들지 않겠습니다.
프로퍼티의 필요한 값들을 채운 후에 Connect, SendMail, Disconnect 를 차례로 호
출하면 됩니다.
음.. 제가 예전에 만든 cgi중에 째즈보드라는게 있는데요, 이놈의 소스를 자료실
에 올린다는게, 출근하면서 디스켓을 자꾸 빠뜨리고 다녀서요.. 며칠내로 올리도
록 하겠습니다.
게시판, 자료실, 방명록, 카운터, 폼메일, 메일링리스트 관리등의 기능들이 들어
있는데요, 몇 년전에 만든거라놔서 저두 내용을 잘 몰라요. 얼마전에 자료실에
올린 라이브러리 파일은 제가 요즘 쓰는거구, 조만간 올라갈 째즈보드 안에도
같은 이름의 유닛들이 들어 있지만 덮어씌우거나 하지는 마세요.. 책임을 못져
요.
그럼 또 다음에..
---------------------------------------------------------------------------
4월 10일 수정분..
---------------------------------------------------------------------------
^^;; 하이텔에도 글 수정이되는군요.. 좋아졌네요..
조기 위에 FinalHeader 란 프로퍼티에 대한 설명이 잘못되었습니다.
이건 어저께 제가 한글인코딩 문제때문에 이리저리해보다 알아낸건데요.
FinalHeader 에는 SMTP프로토콜에 접속하기위한 헤더정보가 들어있습니다.
물론 그냥은 안보입니다.
OnSendStart 라는 이벤트가 있죠.
실제로 전송하기 직전에 들리는 곳인데, 여기서 바로 저 헤더정보를
열람하고 수정할 수 있습니다.
이 NMSMTP는 charset 을 설정하는 기능이 없기때문에,
한글이 제대로 되려면 OnSendStart 에서 FinalHeader의 내용을 바꿔줘야
하더군요.
FinalHeader.Text 를 보면 컨텐츠 타입뒤에 바로 붙어서
charset="us-ascii" 라고 들어가는데,
여기서 us-ascii 를 빼고 원하는 문자셋을 넣으면 됩니다.
한글 이메일 문자셋에 관해서는 여러모로 논란이 많은데요.
1. euc-kr
2. ks_c_5601-1987
3. iso-2022-kr
이 세가지 중에서 암거나 써보고 자신의 편지함으로 메일을 날려서
한글이 제대로 수신이 되는걸 쓰면 되겠습니다.
저는 Outlook 을 쓰는데, ks_c_5601-1987 로 쓰니깐 한글이 잘 날아오더군요.
참고로 제가 사용한 소스 부분입니다.
procedure TWebModule1.NMSMTPSendStart(Sender: TObject);
begin
TNMSMTP(Sender).FinalHeader.Text :=
ExchangeChar(TNMSMTP(Sender).FinalHeader.Text, 'charset=us-ascii',
'charset=ks_c_5601-1987');
end;
Procedure TWebModule1.SendMailToManager(Mail : String);
begin
with TNMSMTP.Create(Self) do begin
OnSendStart := NMSMTPSendStart;
Host := _SMTP_Server;
UserID := _SMTP_User;
EncodeType := uuMime;
SubType := mtHtml;
PostMessage.FromName := 'Miniyou CGI';
PostMessage.FromAddress := Master.FieldByName('MANAGER_EMAIL').AsString;
PostMessage.ToAddress.Add(Master.FieldByName('MANAGER_EMAIL').AsString);
PostMessage.Subject := '주문들어 왔다.. 몇달만이냐.';
PostMessage.Body.Text := Mail;
Connect;
SendMail;
Disconnect;
Free;
end;
end;
이건 주문이 들어올때, 관리자에게 확인해보라고 그냥 날려주는 겁니다.
그럼 이만..