Небольшая проблема с IO::Socket server и данными POST
По некоторым причинам я могу использовать только IO::Socket для сборки своего маленького http-сервера (но не для других выделенных модулей).
EDIT1: я отредактировал свой вопрос, я хочу знать, что я могу поставить вместо закомментированной строки "#last ..."
Вот мой сценарий:
use strict;
use IO::Socket;
my $server = IO::Socket::INET->new(LocalPort => 6800,
Type => SOCK_STREAM,
Reuse => 1,
Listen => 10) or die "$@\n";
my $client ;
while ( $client = $server->accept()) {
my $client_info;
while(<$client>) {
#last if /^\r\n$/;
print "received: '" . $_ . "'\n";
$client_info .= $_;
}
print $client "HTTP/1.0 200 OK\r\n";
print $client "Content-type: text/html\r\n\r\n";
print $client '<H1>Hello World(!), from a perl web server</H1>';
print $client '<br><br>you sent:<br><pre>' . $client_info . '</pre>';
close($client);
}
Теперь, когда я отправляю запрос POST, он (скрипт) не учитывает последнюю строку (данные POST):
wget -qO- --post-data='hello=ok' http://127.0.0.1:6800
<H1>Hello World(!), from a perl web server</H1><br><br>you sent:<br><pre>POST / HTTP/1.1
User-Agent: Wget/1.14 (linux-gnu)
Accept: */*
Host: 127.0.0.1:6800
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 8
</pre>
Выходные данные скрипта:
perl server.pl
received: 'POST / HTTP/1.1
'
received: 'User-Agent: Wget/1.14 (linux-gnu)
'
received: 'Accept: */*
'
received: 'Host: 127.0.0.1:6800
'
received: 'Connection: Keep-Alive
'
received: 'Content-Type: application/x-www-form-urlencoded
'
received: 'Content-Length: 8
'
1 ответ
Этого следовало ожидать. POST-запрос выглядит так
POST / HTTP/1.1
Header: Value
Data=Value
Вы заканчиваете обработку после окончания заголовка, но данные находятся в теле!
Если вы действительно хотите написать свой собственный HTTP-сервер, вы должны извлечь HTTP-метод из заголовка. Если это POST
, вы можете посмотреть на значение из Content-length
заголовок и прочитайте это количество байтов:
read $client, my $post_data, $content_length;
WRT обновленный вопрос:
Если вы хотите создать производственный HTTP-сервер, у вас будут плохие времена. Это сложно. Пожалуйста, прочитайте perlipc
который охватывает тему серверов TCP. Затем вы можете реализовать подмножество HTTP поверх этого.
Также прочитайте модули на CPAN, которые реализуют серверы. Даже если вы не можете скомпилировать модули в своей системе, вы можете использовать модули с чистым Perl или найти части кода, которые вы можете использовать повторно. Большие части CPAN могут использоваться по лицензии GPL.
Если вы хотите сделать это, сделайте это правильно. Напишите себе подпрограмму, которая анализирует HTTP-запрос. Вот эскиз, который не обрабатывает закодированные поля и т.д.:
use strict; use warnings; use autodie;
BEGIN { die "Untested code" }
package Local::HTTP::Request {
sub new {
my ($class, $method, $path, $version, $header_fields, $content) = @_;
...;
}
...; # accessors
sub new_from_fh {
my ($class, $fh) = @_;
local $/ = "\015\102"; # CRLF line endings
chomp(my $first_line = <$fh>);
my ($method, $path, $version) = ...; # parse the $first_line
# this cute little sub parses a single field incl. continuation
# and returns the next line as well.
my $parse_a_field = sub {
chomp(my $line = shift);
my ($name, $value) = split /:\s+/, $line, 2;
while(defined(my $nextline = <$fh>)) {
# handle line continuation
if ($nextline =~ s/^[ \t]//) {
chomp $nextline;
$value .= $nextline;
} else {
return $name, $value, $nextline;
}
}
};
my %fields;
my $line = <$fh>;
until ($line eq $/) {
(my $name, my $value, $line) = $parse_a_field->($line);
$fields{lc $name} = $value;
}
read $fh, my $content, $fields{"content-length"} // 0;
return $class->new( ... );
}
}
Тогда в вашем accept
цикл:
my $request = Local::HTTP::Request->new_from_fh($client);
print $client "HTTP/1.0 200 OK", "\015\012";
print $client "Content-type: text/plain", "\015\012";
print $client "\015\012";
print $client "Request body:\n";
print $client $request->content;