Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Blocking behaviour on Net::HTTP::Methods my_readline()

Abierto
#95 0 comentarios 0 reacciones 0 asignados Ver en GitHub

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

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de libwww-perl/Net-HTTP

Todos los issues de libwww-perl/Net-HTTP

Issues similares

Más issues de Perl

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.