четверг, апреля 05, 2007

LWP и задание ip client'а.

Задача: браузить из под perl с помощью LWP но с разными ip клиента задаваемыми определённым образом.


Для тех кто хочет просто забиндить ip можно не напрягаться и просто написать где нужно: @LWP::Protocol::http::EXTRA_SOCK_OPTS = (LocalAddr => $ip);. Тем же кому нужно некое централизованное решение надо сделать "нечто совсем другое" (c).

Поиском вышел на эту статью и быстренько слабал имплементоры для http и https на базе LWP::Protocol::http и LWP::Protocol::https, вот их схематичная реализация:


package myIp;

use strict;
use warnings;
use vars qw( @ISA $VERSION );
use IO::Socket::Socks;
use LWP::Protocol::http;

@ISA = qw( LWP::Protocol::http );

$VERSION = 0.01;
our $MAX_CONNECT_ATTEMPTS = 1;

sub _new_socket {
my ( $self, $host, $port, $timeout ) = @_;
my $conn_cache = $self->{ua}{conn_cache};
if ($conn_cache) {
if ( my $sock = $conn_cache->withdraw( "http", "$host:$port" ) ) {
return $sock if $sock && !$sock->can_read(0);

# if the socket is readable, then either the peer has closed the
# connection or there are some garbage bytes on it. In either
# case we abandon it.
$sock->close;
}
}

local ($^W) = 0; # IO::Socket::INET can be noisy
my $sock = $self->getSocket( $host, $port );

unless ($sock) {

# IO::Socket::INET leaves additional error messages in $@
die "Can't connect to $host:$port ($@)";
}

# perl 5.005's IO::Socket does not have the blocking method.
eval { $sock->blocking(0); };
$sock;
}

sub getSocket
{
my($self, $host, $port, $level) = @_;

if (!defined($level))
{
$level = 0;
}

my $socket = $self->socket_class->new(
PeerPort => $port,
PeerAddr => $host,
Proto => 'tcp',
LocalAddr => 'XX.XX.XX.XX',
Timeout => '5'
);

if (!$socket && ($level < $MAX_CONNECT_ATTEMPTS)) { $socket = $self->getSocket($host, $port, $level + 1);
}

return $socket;
}

#-----------------------------------------------------------
package myIp::Socket;

use strict;
use warnings;
use vars qw( @ISA );

@ISA = qw(LWP::Protocol::http::SocketMethods Net::HTTP::Methods IO::Socket::INET);

sub configure {
my ($self, $cnf) = @_;
$self->http_configure($cnf);
}
sub http_connect {
my ($self, $cnf) = @_;
$self->SUPER::configure($cnf);
}

1;



package myIpHttps;

use strict;
use warnings;
use vars qw( @ISA $VERSION );
use IO::Socket::Socks;
use LWP::Protocol::https;

@ISA = qw( myIp );

$VERSION = 0.01;
sub _check_sock {
my ( $self, $req, $sock ) = @_;
my $check = $req->header("If-SSL-Cert-Subject");
if ( defined $check ) {
my $cert = $sock->get_peer_certificate
|| die "Missing SSL certificate";
my $subject = $cert->subject_name;
die "Bad SSL certificate subject: '$subject' !~ /$check/"
unless $subject =~ /$check/;
$req->remove_header("If-SSL-Cert-Subject"); # don't pass it on
}
}

sub _get_sock_info {
my $self = shift;
$self->SUPER::_get_sock_info(@_);
my ( $res, $sock ) = @_;
$res->header( "Client-SSL-Cipher" => $sock->get_cipher );
my $cert = $sock->get_peer_certificate;
if ($cert) {
$res->header( "Client-SSL-Cert-Subject" => $cert->subject_name );
$res->header( "Client-SSL-Cert-Issuer" => $cert->issuer_name );
}
if ( !eval { $sock->get_peer_verify } ) {
$res->header( "Client-SSL-Warning" => "Peer certificate not verified" );
}
}

#-----------------------------------------------------------
package myIpHttps::Socket;

use vars qw(@ISA);
require Net::HTTPS;
@ISA = qw(Net::HTTPS LWP::Protocol::http::SocketMethods);

1;


В http для LocalAddr воткнул заглушку 'XX.XX.XX.XX' ну туда можно(и нужно) воткнуть вызов функции необходимый для ваших задач. Https это копия LWP::Protocol::https только унаследованная от myIp.

Комменты я оставил родные от LWP::Protocol::*, так проще ориентироваться где и что поменяли по сравнению с первоисточником. Естественно в зависимости от задач модифицировать придётся больше, это же просто демонстрационный пример.

P.S. В итоге от LWP всё равно отказался. Убог местами для сложных задач.

5 комментариев:

Анонимный комментирует...

В чем собственно выигрыш и зачем там оставлено подключение соксового сокета?

Лаврентий Палыч комментирует...

Уже сложно вспомнить, но соксовый сокет вроде оставлен потому что ip были подняты на разных машинах как socks proxy. Почему я об этом не написал даже и не знаю - исправлять не буду, пусть останется в назидание. :) Причём всё было сложнее несколько - тут я может упрощал для примера и в итоге написал очень коряво.

Petya комментирует...
Этот комментарий был удален администратором блога.
Petya комментирует...
Этот комментарий был удален администратором блога.
123 комментирует...
Этот комментарий был удален администратором блога.