Blocking behaviour on Net::HTTP::Methods my_readline()
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 45/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Tranquilo
- Stack tecnológico
- perl
- Área
- networking
Línea de trabajo
Comienza en Methods.pm, en my_readline(), y ejecuta el script de reproducción de HTTP::Async proporcionado contra la respuesta grande. Compara su comportamiento con el ejemplo de LWP::UserAgent y examina cómo interactúan Net::HTTPS::NB y los sockets bloqueantes. Se considera terminado cuando las lecturas no bloqueantes de HTTP::Async ya no esperan a la respuesta completa sin romper el comportamiento habitual de LWP::UserAgent.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Hi
I'd like to report what I think might be a problem with Net::HTTP::Methods. Or for another pair of eyes to check my conclusions!
I'm using HTTP::Async to write an OpenAPI client.
The client needs to be non-blocking when it fetches the HTTPS API responses, hence the use of HTTP::Async.
For most requests this works well, but on very large API responses HTTP::Async's $async->next_response becomes blocking, which should not happen.
It seems that HTTP::Async is not maintained right now, which is unfortunate, but as far as I can see it is correctly using Net::HTTPS::NB; it uses IO::Select's can_read() before calling Net::HTTPS::NB's read_response_headers(), which ends up calling the same function in Net::HTTP::Methods and there the code blocks in my_readline().
Initially I made a dirty fix in Methods.pm function my_readline():
# consume all incoming bytes
$self->blocking(0); # <===================== My hack!
my $bytes_read = $self->sysread($_, 1024, length);
if(defined $bytes_read) {
$new_bytes += $bytes_read;
}
elsif($!{EINTR} || $!{EAGAIN} || $!{EWOULDBLOCK}) {
return; # <============================ My hack!
redo READ;
}
- I made the socket non-blocking before the sysread: $self->blocking(0);
- I replaced "redo READ;" with "return;"
Then my application code worked as expected and HTTP::Async's $async->next_response no longer blocked on large API responses.
But the same code written with LWP::UserAgent then was broken; not good!
For the first part, I've come to the conclusion that HTTP::Async should really be making the socket non-blocking itself, and I will try and raise an issue there. Though fortunately I can poke through into the socket from my application code and make that happen without making any changes in HTTP::Async.
But I'm still stuck in that I need a change in Net::HTTP::Methods my_readline() to return immediately rather than "redo READ;".
For now I've made this change:
# consume all incoming bytes
my $bytes_read = $self->sysread($_, 1024, length);
if(defined $bytes_read) {
$new_bytes += $bytes_read;
}
elsif($!{EINTR} || $!{EAGAIN} || $!{EWOULDBLOCK}) {
return if ref($self) =~ /Net::HTTPS?::NB/; # Modified by Ludovico Stevens; need to return for non-blocking use
redo READ;
}
I'm not that familiar with the wider Net::HTTP modules and whether this is the right fix.
But I would like to see a more general fix if possible rolled into Net::HTTP's mainline, rather than myself having to go hack the Methods.pm file every time I port my application code.
Below is the test script which reproduces the problem at hand. To verify the non-blocking behaviour this script simply prints dots while it waits for a response.
use strict;
use warnings;
use HTTP::Async;
use HTTP::Request;
use IO::Socket::SSL;
use Cpanel::JSON::XS;
my $host = "10.8.1.8";
sub httpRequest { # Build the OpenAPI HTTP request object
my ($host, $port, $protocol, $method, $path, $token, $body) = @_;
my $url = sprintf("%s://%s:%s/rest/openapi%s",
$protocol,
$host,
$port,
$path
);
my @header = ('Content-Type' => 'application/json; charset=UTF-8', 'Accept' => 'application/json');
push @header, 'X-Auth-Token' => $token if $token;
my $encoded_body = defined $body ? encode_json( $body ) : undef;
return HTTP::Request->new($method, $url, \@header, $encoded_body);
}
sub waitForOpenAPIResponse { # Waits for and returns the next HTTP::Async response, printing progress dots while waiting
my $async = shift;
my $count = 0;
my $response;
while ( $async->not_empty ) {
if ( $response = $async->next_response ) {
print "\nGot: ", $response->code, " ", $response->message, " ", $response->header("Server"),"\n";
print "Size of response = ", length($response->content), "\n";
}
elsif (++$count > 1000) {
$| = 1;
print ".";
$count = 0;
}
}
if (!$response->is_success) { # HTTP Request failed
die("HTTP Request failed: ", $response->status_line);
}
return $response;
}
# Create HTTP::Async object
my $httpAsync = HTTP::Async->new(
ssl_options => { SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE }
);
# Send login request to obtain token
print "OpenAPI login ";
my $request = httpRequest(
$host, 9443, 'https', 'POST', '/auth/token', undef,
{username => "rwa", password => "rwa"}
);
$httpAsync->add( $request );
my $response = waitForOpenAPIResponse($httpAsync);
# Get token from response
my $token = decode_json($response->content)->{token};
print "OpenAPI pulling /v0/configuration ";
$request = httpRequest(
$host, 9443, 'https', 'GET', '/v0/configuration', $token
);
$httpAsync->add( $request );
($httpAsync->_io_select->handles)[0]->blocking(0);
$response = waitForOpenAPIResponse($httpAsync);
The last line but one is me poking the socket to be non-blocking
With the unmodified my_readline() in Net::HTTP::Methods I get this:
C:\Users\lstevens\Scripts\acli\working-dir>http-async-test.pl
OpenAPI login
Got: 200 OK Werkzeug/2.1.2 Python/3.10.13
Size of response = 272
OpenAPI pulling /v0/configuration .<LOOONG WAIT!>........
Got: 200 OK Werkzeug/2.1.2 Python/3.10.13
Size of response = 365603
I inserted "<LOONG WAIT!>"; after the 1st dot basically HTTP::Async's $async->next_response blocks immediately and only returns once almost the entire API response is received.
While with my change in my_readline(), I get the desired behaviour:
C:\Users\lstevens\Scripts\acli\working-dir>http-async-test.pl
OpenAPI login .
Got: 200 OK Werkzeug/2.1.2 Python/3.10.13
Size of response = 272
OpenAPI pulling /v0/configuration .............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Got: 200 OK Werkzeug/2.1.2 Python/3.10.13
Size of response = 365603
And this is the same script using LWP::UserAgent, to make sure I have not broken anything for non users of HTTP::Async :
use strict;
use warnings;
use LWP::UserAgent ();
use HTTP::Request;
use IO::Socket::SSL;
use Cpanel::JSON::XS;
my $host = "10.8.1.8";
sub httpRequest { # Build the OpenAPI HTTP request object
my ($host, $port, $protocol, $method, $path, $token, $body) = @_;
my $url = sprintf("%s://%s:%s/rest/openapi%s",
$protocol,
$host,
$port,
$path
);
my @header = ('Content-Type' => 'application/json; charset=UTF-8', 'Accept' => 'application/json');
push @header, 'X-Auth-Token' => $token if $token;
my $encoded_body = defined $body ? encode_json( $body ) : undef;
return HTTP::Request->new($method, $url, \@header, $encoded_body);
}
# Create LWP::UserAgent object
my $ua = LWP::UserAgent->new(
ssl_opts => { SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE, verify_hostname => 0 }
);
# Send login request to obtain token
print "OpenAPI login\n";
my $request = httpRequest(
$host, 9443, 'https', 'POST', '/auth/token', undef,
{username => "rwa", password => "rwa"}
);
my $response = $ua->request( $request );
print "Got: ", $response->code, " ", $response->message, " ", $response->header("Server"),"\n";
print "Size of response = ", length($response->content), "\n";
if (!$response->is_success) { # HTTP Request failed
die("HTTP Request failed: ", $response->status_line);
}
# Get token from response
my $token = decode_json($response->content)->{token};
print "OpenAPI pulling /v0/configuration\n";
$request = httpRequest(
$host, 9443, 'https', 'GET', '/v0/configuration', $token
);
$response = $ua->request( $request );
print "Got: ", $response->code, " ", $response->message, " ", $response->header("Server"),"\n";
print "Size of response = ", length($response->content), "\n";
if (!$response->is_success) { # HTTP Request failed
die("HTTP Request failed: ", $response->status_line);
}
Which works correctly with or without my changes in Net::HTTP::Methods my_readline()
C:\Users\lstevens\Scripts\acli\working-dir>lwp-useragent-test.pl
OpenAPI login
Got: 200 OK Werkzeug/2.1.2 Python/3.10.13
Size of response = 272
OpenAPI pulling /v0/configuration
Got: 200 OK Werkzeug/2.1.2 Python/3.10.13
Size of response = 365603
Thanks
Ludovico Stevens
- Lenguaje dominante
- Perl
- Estrellas
- 20
- Forks
- 27
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de libwww-perl/Net-HTTP
-
Dificultad 4/5 3-5 días Aptitud para principiantes 50/100
libwww-perl/Net-HTTP#99 ·
-
t/live.t may failAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 45/100
libwww-perl/Net-HTTP#87 · 2 comentarios ·
-
Improve error messageAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 45/100
libwww-perl/Net-HTTP#79 · 6 comentarios ·
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 48/100
libwww-perl/Net-HTTP#74 ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 25/100
libwww-perl/Net-HTTP#68 · 4 comentarios ·
Todos los issues de libwww-perl/Net-HTTP
Issues similares
-
ProcessCopy sets SyncState of origin Process to not_syncPosiblemente ocupada @stefane2741 la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
Los mantenedores suelen responder en 1 día
-
✏️ Editing - Tags
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
openfoodfacts/openfoodfacts-server#15127 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
FIDO2 (ecdsa-sk / ed25519-sk) ingress keys rejected: missing in get_supported_ssh_algorithms_listAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
ovh/the-bastion#695 ·
-
Range length wrong when an abbreviated range end contains a 0 (`pages = {315-20}` gives `\range{pages}{11}`)Posiblemente ocupada @plk la tomó hace 1 día. AbiertoBug fixedindev
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
-
observablt-ci
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
elastic/golang-crossbuild#840 ·
Los mantenedores suelen responder en 1 día