열일곱번째 날: HTTP 패킷을 까보자

저자

@luzluna - Seoul.pm과 #perl-kr의 육아 전문 컨설턴트, 사회적 기업을 꿈꾸는 커피 매니아자 백수, 현재 제주와 서울을 오가며 몽거스 활동을 하고 있다.

시작하며

XML이나 JSON을 사용하는 프로그램을 작성하다보면 클라이언트에서 어떻게 보냈을 때 서버가 어떤 값을 보내주는지 눈으로 봐가면서 프로그램을 작성하면 좀더 쉬울 때가 있습니다. 이런 경우 브라우저에 Firebug 같은 브라우져 플러그인을 설치해서 확인하거나 좀 더 과격한 방법인 tcpdumpWireshark같은 툴을 사용해서 저수준에서 패킷을 캡쳐하는 방법을 많이 사용하곤 합니다. 하지만 Perl을 이용하면 콘솔에서 간편하게 패킷을 찍어보고 테스트할 수 있습니다.

HTTP Request 살짝 까보기

간단히 트위터의 검색 API를 호출할 때의 요청과 응답을 살펴보죠.

1
2
3
4
5
6
7
8
9
#!/usr/bin/env perl
use LWP::UserAgent;
 
$ua = LWP::UserAgent->new;
 
$ua->add_handler("request_send"sub { shift->dump; return });
$ua->add_handler("response_done", sub { shift->dump; return });
 

GetUser-Agent 딱 두 줄의 요청을 보내는데 어마어마한 양의 응답이 돌아옵니다. 응답결과를 한번 보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ ./test.pl
User-Agent: libwww-perl/5.837
 
(no content)
HTTP/1.1 200 OK
Cache-Control: max-age=15, must-revalidate, max-age=1800
Connection: close
Date: Fri, 10 Dec 2010 07:01:44 GMT
Via: 1.1 varnish
Age: 0
Server: hi
Vary: Accept-Encoding
Content-Length: 21719
Content-Type: application/atom+xml; charset=utf-8
Expires: Fri, 10 Dec 2010 07:31:44 GMT
Client-Date: Fri, 10 Dec 2010 07:01:44 GMT
Client-Peer: 199.16.156.11:80
Client-Response-Num: 1
Status: 200 OK
X-Cache: MISS
X-Cache-Svr: slc1-aaf-36-sr1.prod.twitter.com
X-Runtime: 0.07108
X-Served-By: slc1-aaf-36-sr1.prod.twitter.com
X-Served-From: slc1-adf-25-sr1
X-Timeline-Cache-Hit: Hit
X-Varnish: 1489236086
 
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:google="http://base.google.com/ns/1.0" xml:lang="en-US" xmlns:openSe                                             arch="http://a9.com/-/spec/opensearch/1.1/" xmlns="http://www.w3.org/2005/Atom"                                              xmlns:twitter="http://api.twitter.com/">
    <id>tag:search.twitter.com,2005:search/@JEEN_LEE</id>
    <link type="application/atom+xml" rel="self" href="http://search.twitter.com/s                                             earch.atom?q=@JEEN_LEE"/>
    <title>@JEE...
(+ 21207 more bytes not shown)

그런데 헙...! (+ 21207 more bytes not shown)라고 나오네요? 대충 짧은 컨텐츠 확인할땐 상관없겠지만 모든 메시지를 확인할 때는 문제가 되겠죠?

1
2
3
4
5
6
7
8
9
#!/usr/bin/env perl
use LWP::UserAgent;
 
$ua = LWP::UserAgent->new;
 
$ua->add_handler("request_send"sub { print shift->as_string; return });
$ua->add_handler("response_done"sub { print shift->as_string; return });
 

대충 위와 비슷한데 모두 다 출력 됩니다. 여기서는 너무 길어지니까 그냥 생략하도록 하겠습니다.

Perl 모듈도 살짝 까보기

앞의 방법을 활용하면 LWP를 사용하는 Perl 모듈들에서 어떤 방식으로 통신을 주고 받는지 살펴볼 수 있습니다. 이 방법은 제가 트위터나 페이스북 클라이언트를 만들 때 처음부터 문서를 읽고 만들기는 번거로우니까 잘 돌아가는 예제를 보면서 하려고 쓰던 방법입니다.

CPAN의 Net::Twitter::Lite 모듈을 사용하는 트위터 모듈을 살짝 살펴보죠.

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env perl
use Net::Twitter::Lite;
 
my $user = 'test';
my $password = 'test';
my $nt = Net::Twitter::Lite->new(
    username => $user,
    password => $password
);
 
my $friends = eval { $nt->friends() };

최근 트위터가 basic auth를 막아버리면서 제대로 가져오지는 못하지만 예제로 사용할만한 자료를 덤프하는데는 문제가 없습니다. 일단 Net::Twitter::Lite 모듈 파일을 찾아봅니다. 시스템마다 다르지만 perldoc -l Net::Twitter::Lite 명령을 실행시키면 해당 모듈이 설치된 위치를 정확히 찾을 수 있습니다. 모듈 파일을 열어서 보면 다음과 같은 부분이 보입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
    @{$new}{qw/username password/} = $nrc->lpa;
}
 
$new->{ua} ||= do {
    eval "use $new->{useragent_class}";
    croak $@ if $@;
 
    $new->{useragent_class}->new(%{$new->{useragent_args}});
};
 
$new->{ua}->agent($new->{useragent});
$new->{ua}->default_header('X-Twitter-Client'         => $new->{clientname});
...

ua를 생성하네요. 그럼 저기서 만들어진 ua 변수에 핸들러를 추가하면 되겠죠?

1
2
$new->{ua}->add_handler("request_send"sub { print shift->as_string; return });
$new->{ua}->add_handler("response_done"sub { print shift->as_string; return });

앞의 두 줄을 적당히 $new->{ua}가 생성된 다음 위치에 끼워넣습니다. 나중에 제거하기 쉽게 주석도 앞뒤로 좀 달아놓구요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
    @{$new}{qw/username password/} = $nrc->lpa;
}
 
$new->{ua} ||= do {
    eval "use $new->{useragent_class}";
    croak $@ if $@;
 
    $new->{useragent_class}->new(%{$new->{useragent_args}});
};
# For Debug Temp Start
$new->{ua}->add_handler("request_send"sub { print shift->as_string; return });
$new->{ua}->add_handler("response_done"sub { print shift->as_string; return });
# For Debug Temp End
 
$new->{ua}->agent($new->{useragent});
$new->{ua}->default_header('X-Twitter-Client'         => $new->{clientname});
...

이제 한번 실행해보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ ./test.pl
GET http://api.twitter.com/1/statuses/friends.json
Authorization: Basic dGVzdDp0ZXN0
User-Agent: Net::Twitter::Lite/0.10003 (Perl)
X-Twitter-Client: Net::Twitter::Lite
X-Twitter-Client-URL: http://search.cpan.org/dist/Net-Twitter-Lite/
X-Twitter-Client-Version: 0.10003
 
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache, max-age=300
Connection: close
Date: Fri, 10 Dec 2010 08:07:35 GMT
Server: hi
Vary: Accept-Encoding
WWW-Authenticate: Basic realm="Twitter API"
Content-Length: 63
Content-Type: application/json; charset=utf-8
Expires: Fri, 10 Dec 2010 08:12:35 GMT
Client-Date: Fri, 10 Dec 2010 08:07:35 GMT
Client-Peer: 128.242.245.157:80
Client-Response-Num: 1
Set-Cookie: k=112.155.238.14.1291968455525726; path=/; expires=Fri, 17-Dec-10 08:07:35 GMT; domain=.twitter.com
Set-Cookie: guest_id=129196845553039972; path=/; expires=Sun, 09 Jan 2011 08:07:35 GMT
Set-Cookie: _twitter_sess=BAh7CDoPY3JlYXRlZF9hdGwrCIMjUs8sAToHaWQiJWYyZjZhNWYwMDFmYzhl%250AZDE0MWMyOThiODc1NDg0MjZkIgpmbGFzaElDOidBY3Rpb25Db250cm9sbGVy%250AOjpGbGFzaDo6Rmxhc2hIYXNoewAGOgpAdXNlZHsA--60134459dd401b8f8191b8d49340f51edbea6528; domain=.twitter.com; path=/
Status: 401 Unauthorized
X-Runtime: 0.02616
 
{"errors":[{"code":32,"message":"Could not authenticate you"}]}

인증 실패 했다고 오류가 나네요. 비밀번호를 정확하게 넣어도 basic auth는 지원하지 않아 오류가 나니까 괜히 고민하지 마시구요. ;-)