changeset 948:4dc302d8e04f

Tests: changed HTTP2 package to act as a class. Stopped exporting any subroutines. A subset of them now act as class methods.
author Sergey Kandaurov <pluknet@nginx.com>
date Fri, 17 Jun 2016 11:36:33 +0300
parents b9e42c554ba7
children c657aaffdaa8
files h2.t h2_cache.t h2_fastcgi_request_buffering.t h2_headers.t h2_limit_conn.t h2_limit_req.t h2_priority.t h2_proxy_protocol.t h2_proxy_request_buffering.t h2_proxy_request_buffering_ssl.t h2_proxy_ssl.t h2_request_body.t h2_server_tokens.t h2_ssl.t h2_ssl_verify_client.t h2_variables.t lib/Test/Nginx/HTTP2.pm
diffstat 17 files changed, 863 insertions(+), 844 deletions(-) [+]
line wrap: on
line diff
--- a/h2.t
+++ b/h2.t
@@ -18,7 +18,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame :io /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -187,8 +187,8 @@ like($r, qr!Upgrade: h2c!, 'upgrade - to
 
 # SETTINGS
 
-my $sess = new_session(8080, pure => 1);
-my $frames = h2_read($sess, all => [
+my $s = Test::Nginx::HTTP2->new(8080, pure => 1);
+my $frames = $s->read(all => [
 	{ type => 'WINDOW_UPDATE' },
 	{ type => 'SETTINGS'}
 ]);
@@ -204,10 +204,10 @@ ok($frame, 'SETTINGS frame');
 is($frame->{flags}, 0, 'SETTINGS flags');
 is($frame->{sid}, 0, 'SETTINGS stream');
 
-h2_settings($sess, 1);
-h2_settings($sess, 0);
+$s->h2_settings(1);
+$s->h2_settings(0);
 
-$frames = h2_read($sess, all => [{ type => 'SETTINGS' }]);
+$frames = $s->read(all => [{ type => 'SETTINGS' }]);
 
 ($frame) = grep { $_->{type} eq 'SETTINGS' } @$frames;
 ok($frame, 'SETTINGS frame ack');
@@ -215,8 +215,8 @@ is($frame->{flags}, 1, 'SETTINGS flags a
 
 # PING
 
-h2_ping($sess, 'SEE-THIS');
-$frames = h2_read($sess, all => [{ type => 'PING' }]);
+$s->h2_ping('SEE-THIS');
+$frames = $s->read(all => [{ type => 'PING' }]);
 
 ($frame) = grep { $_->{type} eq "PING" } @$frames;
 ok($frame, 'PING frame');
@@ -229,29 +229,29 @@ is($frame->{sid}, 0, 'PING stream');
 SKIP: {
 skip 'long tests', 6 unless $ENV{TEST_NGINX_UNSAFE};
 
-push my @sess, new_session(8089, pure => 1);
-push @sess, new_session(8089, pure => 1);
-h2_ping($sess[-1], 'SEE-THIS');
-push @sess, new_session(8090, pure => 1);
-push @sess, new_session(8090, pure => 1);
-h2_ping($sess[-1], 'SEE-THIS');
+push my @s, Test::Nginx::HTTP2->new(8089, pure => 1);
+push @s, Test::Nginx::HTTP2->new(8089, pure => 1);
+$s[-1]->h2_ping('SEE-THIS');
+push @s, Test::Nginx::HTTP2->new(8090, pure => 1);
+push @s, Test::Nginx::HTTP2->new(8090, pure => 1);
+$s[-1]->h2_ping('SEE-THIS');
 
 select undef, undef, undef, 2.1;
 
-$frames = h2_read(shift @sess, all => [{ type => "GOAWAY" }]);
+$frames = (shift @s)->read(all => [{ type => "GOAWAY" }]);
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'recv timeout - new connection GOAWAY');
 is($frame->{code}, 1, 'recv timeout - new connection code');
 
-$frames = h2_read(shift @sess, all => [{ type => "GOAWAY" }]);
+$frames = (shift @s)->read(all => [{ type => "GOAWAY" }]);
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 is($frame, undef, 'recv timeout - idle connection GOAWAY');
 
-$frames = h2_read(shift @sess, all => [{ type => "GOAWAY" }]);
+$frames = (shift @s)->read(all => [{ type => "GOAWAY" }]);
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 is($frame, undef, 'idle timeout - new connection GOAWAY');
 
-$frames = h2_read(shift @sess, all => [{ type => "GOAWAY" }]);
+$frames = (shift @s)->read(all => [{ type => "GOAWAY" }]);
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'idle timeout - idle connection GOAWAY');
 is($frame->{code}, 0, 'idle timeout - idle connection code');
@@ -260,17 +260,17 @@ is($frame->{code}, 0, 'idle timeout - id
 
 # GOAWAY
 
-h2_goaway(new_session(), 0, 0, 5);
-h2_goaway(new_session(), 0, 0, 5, 'foobar');
-h2_goaway(new_session(), 0, 0, 5, 'foobar', split => [ 8, 8, 4 ]);
+Test::Nginx::HTTP2->new()->h2_goaway(0, 0, 5);
+Test::Nginx::HTTP2->new()->h2_goaway(0, 0, 5, 'foobar');
+Test::Nginx::HTTP2->new()->h2_goaway(0, 0, 5, 'foobar', split => [ 8, 8, 4 ]);
 
-$sess = new_session();
-h2_goaway($sess, 0, 0, 5);
-h2_goaway($sess, 0, 0, 5);
+$s = Test::Nginx::HTTP2->new();
+$s->h2_goaway(0, 0, 5);
+$s->h2_goaway(0, 0, 5);
 
-$sess = new_session();
-h2_goaway($sess, 0, 0, 5, 'foobar', len => 0);
-$frames = h2_read($sess, all => [{ type => "GOAWAY" }]);
+$s = Test::Nginx::HTTP2->new();
+$s->h2_goaway(0, 0, 5, 'foobar', len => 0);
+$frames = $s->read(all => [{ type => "GOAWAY" }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'GOAWAY invalid length - GOAWAY frame');
@@ -283,9 +283,9 @@ is($frame->{code}, 6, 'GOAWAY invalid le
 TODO: {
 local $TODO = 'not yet';
 
-$sess = new_session();
-h2_goaway($sess, 1, 0, 5, 'foobar');
-$frames = h2_read($sess, all => [{ type => "GOAWAY" }], wait => 0.5);
+$s = Test::Nginx::HTTP2->new();
+$s->h2_goaway(1, 0, 5, 'foobar');
+$frames = $s->read(all => [{ type => "GOAWAY" }], wait => 0.5);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'GOAWAY invalid stream - GOAWAY frame');
@@ -296,9 +296,9 @@ is($frame->{code}, 1, 'GOAWAY invalid st
 # client-initiated PUSH_PROMISE, just to ensure nothing went wrong
 # N.B. other implementation returns zero code, which is not anyhow regulated
 
-$sess = new_session();
-raw_write($sess->{socket}, pack("x2C2xN", 4, 0x5, 1));
-$frames = h2_read($sess, all => [{ type => "GOAWAY" }]);
+$s = Test::Nginx::HTTP2->new();
+syswrite($s->{socket}, pack("x2C2xN", 4, 0x5, 1));
+$frames = $s->read(all => [{ type => "GOAWAY" }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'client-initiated PUSH_PROMISE - GOAWAY frame');
@@ -306,9 +306,9 @@ is($frame->{code}, 1, 'client-initiated 
 
 # GET
 
-$sess = new_session();
-my $sid = new_stream($sess);
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+my $sid = $s->new_stream();
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 ok($frame, 'HEADERS frame');
@@ -323,8 +323,8 @@ is($frame->{data}, 'body', 'DATA payload
 
 # GET in the new stream on same connection
 
-$sid = new_stream($sess);
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream();
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{sid}, $sid, 'HEADERS stream 2');
@@ -339,9 +339,9 @@ is($frame->{data}, 'body', 'DATA payload
 
 # HEAD
 
-$sess = new_session();
-$sid = new_stream($sess, { method => 'HEAD' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ method => 'HEAD' });
+$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{sid}, $sid, 'HEAD - HEADERS');
@@ -353,14 +353,14 @@ is($frame, undef, 'HEAD - no body');
 
 # range filter
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t1.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'range', value => 'bytes=10-19', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 206, 'range - HEADERS status');
@@ -371,9 +371,9 @@ is($frame->{data}, '002XXXX000', 'range 
 
 # http2_chunk_size=1
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/chunk_size' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/chunk_size' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 my @data = grep { $_->{type} eq "DATA" } @$frames;
 is(@data, 4, 'chunk_size frames');
@@ -382,17 +382,17 @@ is(join(' ', map { $_->{flags} } @data),
 
 # CONTINUATION
 
-$sess = new_session();
-$sid = new_stream($sess, { continuation => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ continuation => 1, headers => [
 	{ name => ':method', value => 'HEAD', mode => 1 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-h2_continue($sess, $sid, { continuation => 1, headers => [
+$s->h2_continue($sid, { continuation => 1, headers => [
 	{ name => 'x-foo', value => 'X-Bar', mode => 2 }]});
-h2_continue($sess, $sid, { headers => [
+$s->h2_continue($sid, { headers => [
 	{ name => 'referer', value => 'foo', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame, undef, 'CONTINUATION - fragment 1');
@@ -403,22 +403,22 @@ is($frame->{headers}->{'x-referer'}, 'fo
 
 # CONTINUATION - in the middle of request header field
 
-$sess = new_session();
-$sid = new_stream($sess, { continuation => [ 2, 4, 1, 5 ], headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ continuation => [ 2, 4, 1, 5 ], headers => [
 	{ name => ':method', value => 'HEAD', mode => 1 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'CONTINUATION - in header field');
 
 # CONTINUATION on a closed stream
 
-h2_continue($sess, 1, { headers => [
+$s->h2_continue(1, { headers => [
 	{ name => 'x-foo', value => 'X-Bar', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => 1, fin => 1 }]);
+$frames = $s->read(all => [{ sid => 1, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 is($frame->{type}, 'GOAWAY', 'GOAWAY - CONTINUATION closed stream');
@@ -426,23 +426,23 @@ is($frame->{code}, 1, 'GOAWAY - CONTINUA
 
 # frame padding
 
-$sess = new_session();
-$sid = new_stream($sess, { padding => 42, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ padding => 42, headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'padding - HEADERS status');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'padding - next stream');
@@ -452,14 +452,14 @@ is($frame->{headers}->{':status'}, 200, 
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.11');
 
-$sess = new_session();
-$sid = new_stream($sess, { padding => 42, continuation => [ 2, 4, 1, 5 ],
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ padding => 42, continuation => [ 2, 4, 1, 5 ],
 	headers => [
 	{ name => ':method', value => 'GET', mode => 1 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'padding - CONTINUATION');
@@ -468,9 +468,9 @@ is($frame->{headers}->{':status'}, 200, 
 
 # internal redirect
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/redirect' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/redirect' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 405, 'redirect - HEADERS');
@@ -481,9 +481,9 @@ is($frame->{data}, 'body', 'redirect - D
 
 # return 301 with absolute URI
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/return301_absolute' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/return301_absolute' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 301, 'return 301 absolute - status');
@@ -491,9 +491,9 @@ is($frame->{headers}->{'location'}, 'tex
 
 # return 301 with relative URI
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/return301_relative' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/return301_relative' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 301, 'return 301 relative - status');
@@ -502,13 +502,13 @@ is($frame->{headers}->{'location'}, 'htt
 
 # return 301 with relative URI and ':authority' request header field
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/return301_relative', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 301,
@@ -518,13 +518,13 @@ is($frame->{headers}->{'location'}, 'htt
 
 # return 301 with relative URI and 'host' request header field
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/return301_relative', mode => 2 },
 	{ name => 'host', value => 'localhost', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 301,
@@ -534,13 +534,13 @@ is($frame->{headers}->{'location'}, 'htt
 
 # virtual host
 
-$sess = new_session(8085);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8085);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => 'host', value => 'localhost', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -549,12 +549,12 @@ is($frame->{headers}->{':status'}, 200,
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'first', 'virtual host - host - DATA');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -565,12 +565,12 @@ is($frame->{data}, 'first', 'virtual hos
 
 # virtual host - second
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => 'host', value => 'localhost2', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -579,12 +579,12 @@ is($frame->{headers}->{':status'}, 200,
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'second', 'virtual host 2 - host - DATA');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost2', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -595,14 +595,14 @@ is($frame->{data}, 'second', 'virtual ho
 
 # gzip tests for internal nginx version
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/gzip.html' },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'accept-encoding', value => 'gzip' }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'content-encoding'}, 'gzip', 'gzip - encoding');
@@ -613,9 +613,9 @@ gunzip_like($frame->{data}, qr/^SEE-THIS
 
 # charset
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/charset' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/charset' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'content-type'}, 'text/plain; charset=utf-8', 'charset');
@@ -626,10 +626,10 @@ is($frame->{headers}->{'content-type'}, 
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.12');
 
-$sess = new_session(8093);
-$sid = new_stream($sess, { path => '/t2.html', split => [35],
+$s = Test::Nginx::HTTP2->new(8093);
+$sid = $s->new_stream({ path => '/t2.html', split => [35],
 	split_delay => 2.1 });
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
 ok($frame, 'client header timeout');
@@ -637,8 +637,8 @@ is($frame->{code}, 1, 'client header tim
 
 }
 
-h2_ping($sess, 'SEE-THIS');
-$frames = h2_read($sess, all => [{ type => 'PING' }]);
+$s->h2_ping('SEE-THIS');
+$frames = $s->read(all => [{ type => 'PING' }]);
 
 ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames;
 ok($frame, 'client header timeout - PING');
@@ -648,10 +648,10 @@ ok($frame, 'client header timeout - PING
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.12');
 
-$sess = new_session(8093);
-$sid = new_stream($sess, { path => '/proxy/t2.html', body_more => 1 });
-h2_body($sess, 'TEST', { split => [10], split_delay => 2.1 });
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$s = Test::Nginx::HTTP2->new(8093);
+$sid = $s->new_stream({ path => '/proxy/t2.html', body_more => 1 });
+$s->h2_body('TEST', { split => [10], split_delay => 2.1 });
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
 ok($frame, 'client body timeout');
@@ -659,8 +659,8 @@ is($frame->{code}, 1, 'client body timeo
 
 }
 
-h2_ping($sess, 'SEE-THIS');
-$frames = h2_read($sess, all => [{ type => 'PING' }]);
+$s->h2_ping('SEE-THIS');
+$frames = $s->read(all => [{ type => 'PING' }]);
 
 ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames;
 ok($frame, 'client body timeout - PING');
@@ -668,20 +668,20 @@ ok($frame, 'client body timeout - PING')
 
 # proxied request with logging pristine request header field (e.g., referer)
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET' },
 	{ name => ':scheme', value => 'http' },
 	{ name => ':path', value => '/proxy2/' },
 	{ name => ':authority', value => 'localhost' },
 	{ name => 'referer', value => 'foo' }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'proxy with logging request headers');
 
-$sid = new_stream($sess);
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream();
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 ok($frame->{headers}, 'proxy with logging request headers - next');
@@ -693,9 +693,9 @@ ok($frame->{headers}, 'proxy with loggin
 #   created with an initial flow-control window size of 65,535 octets.
 #   The connection flow-control window is also 65,535 octets.
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html' });
-$frames = h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/t1.html' });
+$frames = $s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
 # with the default http2_chunk_size, data is divided into 8 data frames
 
@@ -704,18 +704,18 @@ my $lengths = join ' ', map { $_->{lengt
 is($lengths, '8192 8192 8192 8192 8192 8192 8192 8191',
 	'iws - stream blocked on initial window size');
 
-h2_ping($sess, 'SEE-THIS');
-$frames = h2_read($sess, all => [{ type => 'PING' }]);
+$s->h2_ping('SEE-THIS');
+$frames = $s->read(all => [{ type => 'PING' }]);
 
 ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames;
 ok($frame, 'iws - PING not blocked');
 
-h2_window($sess, 2**16, $sid);
-$frames = h2_read($sess, wait => 0.2);
+$s->h2_window(2**16, $sid);
+$frames = $s->read(wait => 0.2);
 is(@$frames, 0, 'iws - updated stream window');
 
-h2_window($sess, 2**16);
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_window(2**16);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 @data = grep { $_->{type} eq "DATA" } @$frames;
 my $sum = eval join '+', map { $_->{length} } @data;
@@ -729,12 +729,12 @@ is($sum, 81, 'iws - updated connection w
 #   frame that forms part of the connection preface.  The connection
 #   flow-control window can only be changed using WINDOW_UPDATE frames.
 
-$sess = new_session();
-h2_settings($sess, 0, 0x4 => 2**17);
-h2_window($sess, 2**17);
+$s = Test::Nginx::HTTP2->new();
+$s->h2_settings(0, 0x4 => 2**17);
+$s->h2_window(2**17);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 @data = grep { $_->{type} eq "DATA" } @$frames;
 $sum = eval join '+', map { $_->{length} } @data;
@@ -749,15 +749,15 @@ is($sum, 2**16 + 80, 'iws - increased');
 #   controlled frames until it receives WINDOW_UPDATE frames that cause
 #   the flow-control window to become positive.
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-h2_window($sess, 1);
-h2_settings($sess, 0, 0x4 => 42);
-h2_window($sess, 1024, $sid);
+$s->h2_window(1);
+$s->h2_settings(0, 0x4 => 42);
+$s->h2_window(1024, $sid);
 
-$frames = h2_read($sess, all => [{ type => 'SETTINGS' }]);
+$frames = $s->read(all => [{ type => 'SETTINGS' }]);
 
 ($frame) = grep { $_->{type} eq 'SETTINGS' } @$frames;
 ok($frame, 'negative window - SETTINGS frame ack');
@@ -768,14 +768,14 @@ is($frame, undef, 'negative window - no 
 
 # predefined window size, minus new iws settings, minus window update
 
-h2_window($sess, 2**16 - 1 - 42 - 1024, $sid);
+$s->h2_window(2**16 - 1 - 42 - 1024, $sid);
 
-$frames = h2_read($sess, wait => 0.2);
+$frames = $s->read(wait => 0.2);
 is(@$frames, 0, 'zero window - no data');
 
-h2_window($sess, 1, $sid);
+$s->h2_window(1, $sid);
 
-$frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, length => 1 }]);
 is(@$frames, 1, 'positive window');
 
 SKIP: {
@@ -788,13 +788,13 @@ is(@$frames[0]->{length}, 1, 'positive w
 
 # ask write handler in sending large response
 
-$sid = new_stream($sess, { path => '/tbig.html' });
+$sid = $s->new_stream({ path => '/tbig.html' });
 
-h2_window($sess, 2**30, $sid);
-h2_window($sess, 2**30);
+$s->h2_window(2**30, $sid);
+$s->h2_window(2**30);
 
 sleep 1;
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'large response - HEADERS');
@@ -817,8 +817,8 @@ skip 'tolerant operating system', 1 unle
 TODO: {
 local $TODO = 'not yet';
 
-$sid = new_stream($sess);
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream();
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'new stream after large response');
@@ -829,79 +829,79 @@ is($frame->{headers}->{':status'}, 200, 
 
 # write event send timeout
 
-$sess = new_session(8091);
-$sid = new_stream($sess, { path => '/tbig.html' });
-h2_window($sess, 2**30, $sid);
-h2_window($sess, 2**30);
+$s = Test::Nginx::HTTP2->new(8091);
+$sid = $s->new_stream({ path => '/tbig.html' });
+$s->h2_window(2**30, $sid);
+$s->h2_window(2**30);
 
 select undef, undef, undef, 2.1;
 
-h2_ping($sess, 'SEE-THIS');
+$s->h2_ping('SEE-THIS');
 
-$frames = h2_read($sess, all => [{ type => 'PING' }]);
+$frames = $s->read(all => [{ type => 'PING' }]);
 ok(!grep ({ $_->{type} eq "PING" } @$frames), 'large response - send timeout');
 
 # stream with large response queued on write - RST_STREAM handling
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/tbig.html' });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/tbig.html' });
 
-h2_window($sess, 2**30, $sid);
-h2_window($sess, 2**30);
+$s->h2_window(2**30, $sid);
+$s->h2_window(2**30);
 
 select undef, undef, undef, 0.4;
 
-h2_rst($sess, $sid, 8);
-h2_read($sess, all => [{ sid => $sid, fin => 1 }], wait => 0.2);
+$s->h2_rst($sid, 8);
+$s->read(all => [{ sid => $sid, fin => 1 }], wait => 0.2);
 
-$sid = new_stream($sess);
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream();
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{sid}, 3, 'large response - queued with RST_STREAM');
 
 # SETTINGS_MAX_FRAME_SIZE
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/frame_size' });
-h2_window($sess, 2**18, 1);
-h2_window($sess, 2**18);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/frame_size' });
+$s->h2_window(2**18, 1);
+$s->h2_window(2**18);
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 @data = grep { $_->{type} eq "DATA" } @$frames;
 is($data[0]->{length}, 2**14, 'max frame size - default');
 
-$sess = new_session();
-h2_settings($sess, 0, 0x5 => 2**15);
-$sid = new_stream($sess, { path => '/frame_size' });
-h2_window($sess, 2**18, 1);
-h2_window($sess, 2**18);
+$s = Test::Nginx::HTTP2->new();
+$s->h2_settings(0, 0x5 => 2**15);
+$sid = $s->new_stream({ path => '/frame_size' });
+$s->h2_window(2**18, 1);
+$s->h2_window(2**18);
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 @data = grep { $_->{type} eq "DATA" } @$frames;
 is($data[0]->{length}, 2**15, 'max frame size - custom');
 
 # stream multiplexing + WINDOW_UPDATE
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html' });
-$frames = h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/t1.html' });
+$frames = $s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
 @data = grep { $_->{type} eq "DATA" } @$frames;
 $sum = eval join '+', map { $_->{length} } @data;
 is($sum, 2**16 - 1, 'multiple - stream1 data');
 
-my $sid2 = new_stream($sess, { path => '/t1.html' });
-$frames = h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+my $sid2 = $s->new_stream({ path => '/t1.html' });
+$frames = $s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
 @data = grep { $_->{type} eq "DATA" } @$frames;
 is(@data, 0, 'multiple - stream2 no data');
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 }
 ]);
@@ -916,22 +916,22 @@ is($sum, 2**16 + 80, 'multiple - stream2
 
 # http2_max_concurrent_streams
 
-$sess = new_session(8086, pure => 1);
-$frames = h2_read($sess, all => [{ type => 'SETTINGS' }]);
+$s = Test::Nginx::HTTP2->new(8086, pure => 1);
+$frames = $s->read(all => [{ type => 'SETTINGS' }]);
 
 ($frame) = grep { $_->{type} eq 'SETTINGS' } @$frames;
 is($frame->{3}, 1, 'http2_max_concurrent_streams SETTINGS');
 
-h2_window($sess, 2**18);
+$s->h2_window(2**18);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-$frames = h2_read($sess, all => [{ sid => $sid, length => 2 ** 16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$frames = $s->read(all => [{ sid => $sid, length => 2 ** 16 - 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
 is($frame->{headers}->{':status'}, 200, 'http2_max_concurrent_streams');
 
-$sid2 = new_stream($sess, { path => '/t1.html' });
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$sid2 = $s->new_stream({ path => '/t1.html' });
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
 isnt($frame->{headers}->{':status'}, 200, 'http2_max_concurrent_streams 2');
@@ -944,38 +944,38 @@ is($frame->{code}, 7, 'http2_max_concurr
 
 # properly skip header field that's not/never indexed from discarded streams
 
-$sid2 = new_stream($sess, { headers => [
+$sid2 = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET' },
 	{ name => ':scheme', value => 'http' },
 	{ name => ':path', value => '/', mode => 6 },
 	{ name => ':authority', value => 'localhost' },
 	{ name => 'x-foo', value => 'Foo', mode => 2 }]});
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 # also if split across writes
 
-$sid2 = new_stream($sess, { split => [ 22 ], headers => [
+$sid2 = $s->new_stream({ split => [ 22 ], headers => [
 	{ name => ':method', value => 'GET' },
 	{ name => ':scheme', value => 'http' },
 	{ name => ':path', value => '/', mode => 6 },
 	{ name => ':authority', value => 'localhost' },
 	{ name => 'x-bar', value => 'Bar', mode => 2 }]});
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 # also if split across frames
 
-$sid2 = new_stream($sess, { continuation => [ 17 ], headers => [
+$sid2 = $s->new_stream({ continuation => [ 17 ], headers => [
 	{ name => ':method', value => 'GET' },
 	{ name => ':scheme', value => 'http' },
 	{ name => ':path', value => '/', mode => 6 },
 	{ name => ':authority', value => 'localhost' },
 	{ name => 'x-baz', value => 'Baz', mode => 2 }]});
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
-h2_window($sess, 2**16, $sid);
-h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_window(2**16, $sid);
+$s->read(all => [{ sid => $sid, fin => 1 }]);
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET' },
 	{ name => ':scheme', value => 'http' },
 	{ name => ':path', value => '/t2.html' },
@@ -984,7 +984,7 @@ h2_read($sess, all => [{ sid => $sid, fi
 	{ name => 'x-foo', value => 'Foo', mode => 0 },
 	{ name => 'x-bar', value => 'Bar', mode => 0 },
 	{ name => 'x-baz', value => 'Baz', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
 is($frame->{headers}->{':status'}, 200, 'http2_max_concurrent_streams 3');
@@ -994,16 +994,16 @@ is($frame->{headers}->{':status'}, 200, 
 
 # invalid connection preface
 
-$sess = new_session(8080, preface => 'x' x 16, pure => 1);
-$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
+$s = Test::Nginx::HTTP2->new(8080, preface => 'x' x 16, pure => 1);
+$frames = $s->read(all => [{ type => 'GOAWAY' }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'invalid preface - GOAWAY frame');
 is($frame->{code}, 1, 'invalid preface - error code');
 
-$sess = new_session(8080, preface => 'PRI * HTTP/2.0' . CRLF . CRLF . 'x' x 8,
-	pure => 1);
-$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
+my $preface = 'PRI * HTTP/2.0' . CRLF . CRLF . 'x' x 8;
+$s = Test::Nginx::HTTP2->new(8080, preface => $preface, pure => 1);
+$frames = $s->read(all => [{ type => 'GOAWAY' }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'invalid preface 2 - GOAWAY frame');
@@ -1011,9 +1011,9 @@ is($frame->{code}, 1, 'invalid preface 2
 
 # GOAWAY on SYN_STREAM with even StreamID
 
-$sess = new_session();
-new_stream($sess, { path => '/' }, 2);
-$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
+$s = Test::Nginx::HTTP2->new();
+$s->new_stream({ path => '/' }, 2);
+$frames = $s->read(all => [{ type => 'GOAWAY' }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'even stream - GOAWAY frame');
@@ -1026,12 +1026,12 @@ is($frame->{last_sid}, 0, 'even stream -
 #   The first use of a new stream identifier implicitly closes all
 #   streams in the "idle" state <..> with a lower-valued stream identifier.
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/' }, 3);
-h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/' }, 3);
+$s->read(all => [{ sid => $sid, fin => 1 }]);
 
-$sid2 = new_stream($sess, { path => '/' }, 1);
-$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
+$sid2 = $s->new_stream({ path => '/' }, 1);
+$frames = $s->read(all => [{ type => 'GOAWAY' }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'backward stream - GOAWAY frame');
@@ -1040,12 +1040,12 @@ is($frame->{last_sid}, $sid, 'backward s
 
 # GOAWAY on the second SYN_STREAM with same StreamID
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/' });
-h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/' });
+$s->read(all => [{ sid => $sid, fin => 1 }]);
 
-$sid2 = new_stream($sess, { path => '/' }, $sid);
-$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
+$sid2 = $s->new_stream({ path => '/' }, $sid);
+$frames = $s->read(all => [{ type => 'GOAWAY' }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'dup stream - GOAWAY frame');
@@ -1054,52 +1054,52 @@ is($frame->{last_sid}, $sid, 'dup stream
 
 # aborted stream with zero HEADERS payload followed by client connection close
 
-new_stream(new_session(), { split => [ 9 ], abort => 1 });
+Test::Nginx::HTTP2->new()->new_stream({ split => [ 9 ], abort => 1 });
 
 # unknown frame type
 
-$sess = new_session();
-h2_unknown($sess, 'payload');
-h2_ping($sess, 'SEE-THIS');
-$frames = h2_read($sess, all => [{ type => 'PING' }]);
+$s = Test::Nginx::HTTP2->new();
+$s->h2_unknown('payload');
+$s->h2_ping('SEE-THIS');
+$frames = $s->read(all => [{ type => 'PING' }]);
 
 ($frame) = grep { $_->{type} eq "PING" } @$frames;
 is($frame->{value}, 'SEE-THIS', 'unknown frame type');
 
 # GOAWAY - force closing a connection by server
 
-$sid = new_stream($sess);
-h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream();
+$s->read(all => [{ sid => $sid, fin => 1 }]);
 
 # graceful shutdown with stream waiting on HEADERS payload
 
-my $grace = new_session(8089);
-new_stream($grace, { split => [ 9 ], abort => 1 });
+my $grace = Test::Nginx::HTTP2->new(8089);
+$grace->new_stream({ split => [ 9 ], abort => 1 });
 
 # graceful shutdown with stream waiting on WINDOW_UPDATE
 
-my $grace2 = new_session(8089);
-$sid = new_stream($grace2, { path => '/t1.html' });
-h2_read($grace2, all => [{ sid => $sid, length => 2**16 - 1 }]);
+my $grace2 = Test::Nginx::HTTP2->new(8089);
+$sid = $grace2->new_stream({ path => '/t1.html' });
+$grace2->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
 # graceful shutdown waiting on incomplete request body DATA frames
 
-my $grace3 = new_session(8090);
-$sid = new_stream($grace3, { path => '/proxy2/t2.html', body_more => 1 });
-h2_body($grace3, 'TEST', { body_more => 1 });
+my $grace3 = Test::Nginx::HTTP2->new(8090);
+$sid = $grace3->new_stream({ path => '/proxy2/t2.html', body_more => 1 });
+$grace3->h2_body('TEST', { body_more => 1 });
 
 # partial request body data frame with connection close after body timeout
 
-my $grace4 = new_session(8093);
-$sid = new_stream($grace4, { path => '/proxy/t2.html', body_more => 1 });
-h2_body($grace4, 'TEST', { split => [ 12 ], abort => 1 });
+my $grace4 = Test::Nginx::HTTP2->new(8093);
+$sid = $grace4->new_stream({ path => '/proxy/t2.html', body_more => 1 });
+$grace4->h2_body('TEST', { split => [ 12 ], abort => 1 });
 
 select undef, undef, undef, 1.1;
 undef $grace4;
 
 $t->stop();
 
-$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
+$frames = $s->read(all => [{ type => 'GOAWAY' }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'GOAWAY on connection close');
--- a/h2_cache.t
+++ b/h2_cache.t
@@ -16,7 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -66,9 +66,9 @@ EOF
 
 # simple proxy cache test
 
-my $sess = new_session();
-my $sid = new_stream($sess, { path => '/cache/t.html' });
-my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+my $s = Test::Nginx::HTTP2->new();
+my $sid = $s->new_stream({ path => '/cache/t.html' });
+my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, '200', 'proxy cache');
@@ -81,13 +81,13 @@ is($frame->{data}, 'SEE-THIS', 'proxy ca
 
 $t->write_file('t.html', 'NOOP');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/cache/t.html' },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'if-none-match', value => $etag }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 304, 'proxy cache conditional');
@@ -96,26 +96,26 @@ is($frame->{headers}->{':status'}, 304, 
 
 # request body with cached response
 
-$sid = new_stream($sess, { path => '/cache/t.html', body_more => 1 });
-h2_body($sess, 'TEST');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream({ path => '/cache/t.html', body_more => 1 });
+$s->h2_body('TEST');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'proxy cache - request body');
 
-h2_ping($sess, 'SEE-THIS');
-$frames = h2_read($sess, all => [{ type => 'PING' }]);
+$s->h2_ping('SEE-THIS');
+$frames = $s->read(all => [{ type => 'PING' }]);
 
 ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames;
 ok($frame, 'proxy cache - request body - next');
 
 # HEADERS could be received with fin, followed by DATA
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/cache/t.html?1', method => 'HEAD' });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/cache/t.html?1', method => 'HEAD' });
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }], wait => 0.2);
-push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }], wait => 0.2)};
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }], wait => 0.2);
+push @$frames, $_ for @{$s->read(all => [{ sid => $sid }], wait => 0.2)};
 ok(!grep ({ $_->{type} eq "DATA" } @$frames), 'proxy cache HEAD - no body');
 
 # proxy cache - expect no stray empty DATA frame
@@ -123,10 +123,10 @@ ok(!grep ({ $_->{type} eq "DATA" } @$fra
 TODO: {
 local $TODO = 'not yet';
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/cache/t.html?2' });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/cache/t.html?2' });
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 my @data = grep ({ $_->{type} eq "DATA" } @$frames);
 is(@data, 1, 'proxy cache write - data frames');
 is(join(' ', map { $_->{data} } @data), 'SEE-THIS', 'proxy cache write - data');
@@ -136,12 +136,12 @@ is(join(' ', map { $_->{flags} } @data),
 
 # HEAD on empty cache with proxy_buffering off
 
-$sess = new_session();
-$sid = new_stream($sess,
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream(
 	{ path => '/proxy_buffering_off/t.html?1', method => 'HEAD' });
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }], wait => 0.2)};
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
+push @$frames, $_ for @{$s->read(all => [{ sid => $sid }], wait => 0.2)};
 ok(!grep ({ $_->{type} eq "DATA" } @$frames),
 	'proxy cache HEAD buffering off - no body');
 
--- a/h2_fastcgi_request_buffering.t
+++ b/h2_fastcgi_request_buffering.t
@@ -16,7 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame :io /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -197,9 +197,9 @@ sub get_body {
 	)
 		or die "Can't create listening socket: $!\n";
 
-	my $sess = new_session(8080);
+	my $s = Test::Nginx::HTTP2->new();
 	my $sid = exists $extra{'content-length'}
-		? new_stream($sess, { headers => [
+		? $s->new_stream({ headers => [
 			{ name => ':method', value => 'GET' },
 			{ name => ':scheme', value => 'http' },
 			{ name => ':path', value => $url, },
@@ -207,13 +207,13 @@ sub get_body {
 			{ name => 'content-length',
 				value => $extra{'content-length'} }],
 			body_more => 1 })
-		: new_stream($sess, { path => $url, body_more => 1 });
+		: $s->new_stream({ path => $url, body_more => 1 });
 
 	$client = $server->accept() or return;
 
 	log2c("(new connection $client)");
 
-	$f->{headers} = raw_read($client, '', 1, \&log2i);
+	$f->{headers} = backend_read($client);
 
 	my $h = fastcgi_read_record(\$f->{headers});
 	my $version = $h->{version};
@@ -224,13 +224,12 @@ sub get_body {
 		my $len = length($body);
 		my $wait = $extra{wait};
 
-		h2_body($sess, $body, { %extra });
+		$s->h2_body($body, { %extra });
 
 		$body = '';
 
 		for (1 .. 10) {
-			my $buf = raw_read($client, '', 1, \&log2i, $wait)
-				or return '';
+			my $buf = backend_read($client, $wait) or return '';
 
 			while (my $h = fastcgi_read_record(\$buf)) {
 
@@ -260,13 +259,24 @@ EOF
 
 		$client->close;
 
-		my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+		my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 		my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 		return $frame->{headers}->{':status'};
 	};
 	return $f;
 }
 
+sub backend_read {
+	my ($s, $timo) = @_;
+	my $buf = '';
+
+	if (IO::Select->new($s)->can_read($timo || 3)) {
+		$s->sysread($buf, 16384) or return;
+		log2i($buf);
+	}
+	return $buf;
+}
+
 sub log2i { Test::Nginx::log_core('|| <<', @_); }
 sub log2o { Test::Nginx::log_core('|| >>', @_); }
 sub log2c { Test::Nginx::log_core('||', @_); }
--- a/h2_headers.t
+++ b/h2_headers.t
@@ -17,7 +17,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -126,97 +126,97 @@ open STDERR, ">&", \*OLDERR;
 
 # 6.1. Indexed Header Field Representation
 
-my $sess = new_session();
-my $sid = new_stream($sess, { headers => [
+my $s = Test::Nginx::HTTP2->new();
+my $sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'indexed header field');
 
 # 6.2.1. Literal Header Field with Incremental Indexing
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 1, huff => 0 },
 	{ name => ':scheme', value => 'http', mode => 1, huff => 0 },
 	{ name => ':path', value => '/', mode => 1, huff => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal with indexing');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 1, huff => 1 },
 	{ name => ':scheme', value => 'http', mode => 1, huff => 1 },
 	{ name => ':path', value => '/', mode => 1, huff => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal with indexing - huffman');
 
 # 6.2.1. Literal Header Field with Incremental Indexing -- New Name
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 2, huff => 0 },
 	{ name => ':scheme', value => 'http', mode => 2, huff => 0 },
 	{ name => ':path', value => '/', mode => 2, huff => 0 },
 	{ name => ':authority', value => 'localhost', mode => 2, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal with indexing - new');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 2, huff => 1 },
 	{ name => ':scheme', value => 'http', mode => 2, huff => 1 },
 	{ name => ':path', value => '/', mode => 2, huff => 1 },
 	{ name => ':authority', value => 'localhost', mode => 2, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal with indexing - new huffman');
 
 # 6.2.2. Literal Header Field without Indexing
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 3, huff => 0 },
 	{ name => ':scheme', value => 'http', mode => 3, huff => 0 },
 	{ name => ':path', value => '/', mode => 3, huff => 0 },
 	{ name => ':authority', value => 'localhost', mode => 3, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal without indexing');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 3, huff => 1 },
 	{ name => ':scheme', value => 'http', mode => 3, huff => 1 },
 	{ name => ':path', value => '/', mode => 3, huff => 1 },
 	{ name => ':authority', value => 'localhost', mode => 3, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal without indexing - huffman');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 3, huff => 0 },
 	{ name => ':scheme', value => 'http', mode => 3, huff => 0 },
 	{ name => ':path', value => '/', mode => 3, huff => 0 },
 	{ name => ':authority', value => 'localhost', mode => 3, huff => 0 },
 	{ name => 'referer', value => 'foo', mode => 3, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -226,24 +226,24 @@ is($frame->{headers}->{'x-referer'}, 'fo
 
 # 6.2.2. Literal Header Field without Indexing -- New Name
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 4, huff => 0 },
 	{ name => ':scheme', value => 'http', mode => 4, huff => 0 },
 	{ name => ':path', value => '/', mode => 4, huff => 0 },
 	{ name => ':authority', value => 'localhost', mode => 4, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal without indexing - new');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 4, huff => 1 },
 	{ name => ':scheme', value => 'http', mode => 4, huff => 1 },
 	{ name => ':path', value => '/', mode => 4, huff => 1 },
 	{ name => ':authority', value => 'localhost', mode => 4, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -251,36 +251,36 @@ is($frame->{headers}->{':status'}, 200,
 
 # 6.2.3. Literal Header Field Never Indexed
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 5, huff => 0 },
 	{ name => ':scheme', value => 'http', mode => 5, huff => 0 },
 	{ name => ':path', value => '/', mode => 5, huff => 0 },
 	{ name => ':authority', value => 'localhost', mode => 5, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal never indexed');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 5, huff => 1 },
 	{ name => ':scheme', value => 'http', mode => 5, huff => 1 },
 	{ name => ':path', value => '/', mode => 5, huff => 1 },
 	{ name => ':authority', value => 'localhost', mode => 5, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal never indexed - huffman');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 5, huff => 0 },
 	{ name => ':scheme', value => 'http', mode => 5, huff => 0 },
 	{ name => ':path', value => '/', mode => 5, huff => 0 },
 	{ name => ':authority', value => 'localhost', mode => 5, huff => 0 },
 	{ name => 'referer', value => 'foo', mode => 5, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -290,87 +290,87 @@ is($frame->{headers}->{'x-referer'}, 'fo
 
 # 6.2.3. Literal Header Field Never Indexed -- New Name
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 6, huff => 0 },
 	{ name => ':scheme', value => 'http', mode => 6, huff => 0 },
 	{ name => ':path', value => '/', mode => 6, huff => 0 },
 	{ name => ':authority', value => 'localhost', mode => 6, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal never indexed - new');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 6, huff => 1 },
 	{ name => ':scheme', value => 'http', mode => 6, huff => 1 },
 	{ name => ':path', value => '/', mode => 6, huff => 1 },
 	{ name => ':authority', value => 'localhost', mode => 6, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'literal never indexed - new huffman');
 
 # reuse literal with multibyte indexing
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'referer', value => 'foo', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - new');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 0 },
 	{ name => 'referer', value => 'foo', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - indexed');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'x-foo', value => 'X-Bar', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - new');
 
 # reuse literal with multibyte indexing - reused name
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 0 },
 	{ name => 'x-foo', value => 'X-Bar', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - indexed');
 
 # reuse literal with multibyte indexing - reused name only
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 0 },
 	{ name => 'x-foo', value => 'X-Baz', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-sent-foo'}, 'X-Baz',
@@ -378,30 +378,30 @@ is($frame->{headers}->{'x-sent-foo'}, 'X
 
 # response header field with characters not suitable for huffman encoding
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'x-foo', value => '{{{{{', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-sent-foo'}, '{{{{{', 'rare chars');
-like($sess->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding');
+like($s->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding');
 
 # response header field with huffman encoding
 # NB: implementation detail, not obligated
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'x-foo', value => 'aaaaa', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-sent-foo'}, 'aaaaa', 'well known chars');
@@ -409,7 +409,7 @@ is($frame->{headers}->{'x-sent-foo'}, 'a
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.12');
 
-unlike($sess->{headers}, qr/aaaaa/, 'well known chars - huffman encoding');
+unlike($s->{headers}, qr/aaaaa/, 'well known chars - huffman encoding');
 
 }
 
@@ -418,14 +418,14 @@ unlike($sess->{headers}, qr/aaaaa/, 'wel
 
 my $field = pack "C*", ((map { 97 } (1 .. 862)), 1 .. 9, 11, 12, 14 .. 255);
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'x-foo', value => $field, mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-sent-foo'}, $field, 'all chars');
@@ -433,7 +433,7 @@ is($frame->{headers}->{'x-sent-foo'}, $f
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.12');
 
-unlike($sess->{headers}, qr/abcde/, 'all chars - huffman encoding');
+unlike($s->{headers}, qr/abcde/, 'all chars - huffman encoding');
 
 }
 
@@ -443,34 +443,34 @@ unlike($sess->{headers}, qr/abcde/, 'all
 # by maintaining dynamic table space only for index 0
 # 'x-foo' has index 0, and 'referer' has index 1
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'referer', value => 'foo', mode => 1 },
 	{ name => 'x-foo', value => 'X-Bar', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
-$sid = new_stream($sess, { table_size => 61, headers => [
+$sid = $s->new_stream({ table_size => 61, headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => 'x-foo', value => 'X-Bar', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 isnt($frame, undef, 'updated table size - remaining index');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'referer', value => 'foo', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame, undef, 'invalid index');
@@ -499,28 +499,28 @@ is($frame->{code}, 9, 'invalid index - G
 #   The index value of 0 is not used.  It MUST be treated as a decoding
 #   error if found in an indexed header field representation.
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => '', value => '', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ok($frame, 'zero index - GOAWAY');
 is($frame->{code}, 9, 'zero index - GOAWAY COMPRESSION_ERROR');
 
 # invalid table size update
 
-$sess = new_session();
-$sid = new_stream($sess, { table_size => 4097, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ table_size => 4097, headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => 'x-foo', value => 'X-Bar', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'invalid table size - GOAWAY');
@@ -533,15 +533,15 @@ is($frame->{code}, 9, 'invalid table siz
 #   To allow for better compression efficiency, the Cookie header field
 #   MAY be split into separate header fields <..>.
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/cookie', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'cookie', value => 'a=b', mode => 2},
 	{ name => 'cookie', value => 'c=d', mode => 2}]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-cookie-a'}, 'b',
@@ -559,15 +559,15 @@ is($frame->{headers}->{'x-cookie'}, 'a=b
 #   before being passed into a non-HTTP/2 context, such as an HTTP/1.1
 #   connection <..>
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/proxy/cookie', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'cookie', value => 'a=b', mode => 2 },
 	{ name => 'cookie', value => 'c=d', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-sent-cookie'}, 'a=b; c=d',
@@ -581,9 +581,9 @@ is($frame->{headers}->{'x-sent-cookie-c'
 
 # response header field with multiple values
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/set-cookie' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/set-cookie' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'set-cookie'}[0], 'a=b',
@@ -593,9 +593,9 @@ is($frame->{headers}->{'set-cookie'}[1],
 
 # response header field with multiple values from HTTP backend
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy/set-cookie' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/proxy/set-cookie' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'set-cookie'}[0], 'a=b',
@@ -611,10 +611,10 @@ is($frame->{headers}->{'x-uc-c'}, 'd',
 # put three long header fields (not less than SETTINGS_MAX_FRAME_SIZE/2)
 # to break header block into separate frames, one such field per frame
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**13 });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**13 });
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
 my @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
 is(@{$data[-1]->{headers}{'x-longheader'}}, 3,
 	'response CONTINUATION - headers');
@@ -629,10 +629,10 @@ cmp_ok($data[-1], '<=', 2**14, 'response
 
 # same but without response DATA frames
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/continuation/204?h=' . 'x' x 2**13 });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/continuation/204?h=' . 'x' x 2**13 });
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
 is(@{$data[-1]->{headers}{'x-longheader'}}, 3,
 	'no body CONTINUATION - headers');
@@ -647,10 +647,10 @@ cmp_ok($data[-1], '<=', 2**14, 'no body 
 
 # response header block is always split by SETTINGS_MAX_FRAME_SIZE
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**15 });
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
 @data = sort { $a <=> $b } map { $_->{length} } @data;
 cmp_ok($data[-1], '<=', 2**14, 'response header frames limited');
@@ -660,11 +660,11 @@ cmp_ok($data[-1], '<=', 2**14, 'response
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.7');
 
-$sess = new_session(8082);
-h2_settings($sess, 0, 0x5 => 2**17);
+$s = Test::Nginx::HTTP2->new(8082);
+$s->h2_settings(0, 0x5 => 2**17);
 
-$sid = new_stream($sess, { path => '/frame_size?h=' . 'x' x 2**15 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+$sid = $s->new_stream({ path => '/frame_size?h=' . 'x' x 2**15 });
+$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 ok($frame, 'response header - parts');
@@ -679,9 +679,9 @@ is(length join('', @{$frame->{headers}->
 
 # response header block split and sent in parts
 
-$sess = new_session(8082);
-$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+$s = Test::Nginx::HTTP2->new(8082);
+$sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**15 });
+$frames = $s->read(all => [{ sid => $sid, fin => 0x4 }]);
 
 @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
 my ($lengths) = sort { $b <=> $a } map { $_->{length} } @data;
@@ -694,136 +694,136 @@ is(length join('', @{@$frames[-1]->{head
 
 # max_field_size - header field name
 
-$sess = new_session(8084);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8084);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'field name size less');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'field name size second');
 
-$sess = new_session(8084);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8084);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname10' x 2 . 'xx', value => 'value', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'field name size equal');
 
-$sess = new_session(8084);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8084);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname10' x 2 . 'xxx', value => 'value', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 is($frame, undef, 'field name size greater');
 
 # max_field_size - header field value
 
-$sess = new_session(8084);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8084);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'name', value => 'valu5' x 4 . 'x', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'field value size less');
 
-$sess = new_session(8084);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8084);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'name', value => 'valu5' x 4 . 'xx', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'field value size equal');
 
-$sess = new_session(8084);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8084);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'name', value => 'valu5' x 4 . 'xxx', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 is($frame, undef, 'field value size greater');
 
 # max_header_size
 
-$sess = new_session(8085);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8085);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname9', value => 'x', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'header size less');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname9', value => 'x', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'header size second');
 
-$sess = new_session(8085);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8085);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname9', value => 'xx', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'header size equal');
 
-$sess = new_session(8085);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8085);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname9', value => 'xxx', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 is($frame, undef, 'header size greater');
@@ -831,51 +831,51 @@ is($frame, undef, 'header size greater')
 # header size is based on (decompressed) header list
 # two extra 1-byte indices would otherwise fit in max_header_size
 
-$sess = new_session(8085);
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new(8085);
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname9', value => 'x', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'header size new index');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname9', value => 'x', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'DATA' } @$frames;
 ok($frame, 'header size indexed');
 
-$sid = new_stream($sess, { headers => [
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/t2.html', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'longname9', value => 'x', mode => 0 },
 	{ name => 'longname9', value => 'x', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames;
 is($frame->{code}, 0xb, 'header size indexed greater');
 
 # HPACK table boundary
 
-$sess = new_session();
-h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$s->read(all => [{ sid => $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => '', mode => 0 },
 	{ name => 'x' x 2016, value => 'x' x 2048, mode => 2 }]}), fin => 1 }]);
-$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+$frames = $s->read(all => [{ sid => $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
@@ -885,13 +885,13 @@ h2_read($sess, all => [{ sid => new_stre
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 ok($frame, 'HPACK table boundary');
 
-h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+$s->read(all => [{ sid => $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => '', mode => 0 },
 	{ name => 'x' x 33, value => 'x' x 4031, mode => 2 }]}), fin => 1 }]);
-$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+$frames = $s->read(all => [{ sid => $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
@@ -901,13 +901,13 @@ h2_read($sess, all => [{ sid => new_stre
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 ok($frame, 'HPACK table boundary - header field name');
 
-h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+$s->read(all => [{ sid => $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => '', mode => 0 },
 	{ name => 'x', value => 'x' x 64, mode => 2 }]}), fin => 1 }]);
-$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+$frames = $s->read(all => [{ sid => $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
@@ -923,14 +923,14 @@ ok($frame, 'HPACK table boundary - heade
 #   Any request or response that contains a character not permitted
 #   in a header field value MUST be treated as malformed.
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/proxy2/', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'x-foo', value => "x-bar\r\nreferer:see-this", mode => 2 }]});
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 # 10.3.  Intermediary Encapsulation Attacks
 #   An intermediary therefore cannot translate an HTTP/2 request or response
@@ -954,15 +954,15 @@ is($frame->{code}, 1, 'newline in reques
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.7');
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'x_foo', value => "x-bar", mode => 2 },
 	{ name => 'referer', value => "see-this", mode => 1 }]});
-$frames = h2_read($sess, all => [{ type => 'HEADERS' }]);
+$frames = $s->read(all => [{ type => 'HEADERS' }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name');
@@ -974,12 +974,12 @@ is($frame->{headers}->{'x-referer'}, 'se
 TODO: {
 local $TODO = 'not yet';
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 400, 'incomplete headers');
@@ -988,21 +988,21 @@ is($frame->{headers}->{':status'}, 400, 
 
 # empty request header ':authority'
 
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/', mode => 0 },
 	{ name => ':authority', value => '', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 400, 'empty authority');
 
 # client sent invalid :path header
 
-$sid = new_stream($sess, { path => 't1.html' });
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$sid = $s->new_stream({ path => 't1.html' });
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
 is($frame->{code}, 1, 'invalid path');
--- a/h2_limit_conn.t
+++ b/h2_limit_conn.t
@@ -16,7 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -55,42 +55,42 @@ EOF
 
 ###############################################################################
 
-my $sess = new_session();
-h2_settings($sess, 0, 0x4 => 1);
+my $s = Test::Nginx::HTTP2->new();
+$s->h2_settings(0, 0x4 => 1);
 
-my $sid = new_stream($sess, { path => '/t.html' });
-my $frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
+my $sid = $s->new_stream({ path => '/t.html' });
+my $frames = $s->read(all => [{ sid => $sid, length => 1 }]);
 
 my ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
 is($frame->{headers}->{':status'}, 200, 'limit_conn first stream');
 
-my $sid2 = new_stream($sess, { path => '/t.html' });
-$frames = h2_read($sess, all => [{ sid => $sid2, length => 1 }]);
+my $sid2 = $s->new_stream({ path => '/t.html' });
+$frames = $s->read(all => [{ sid => $sid2, length => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
 is($frame->{headers}->{':status'}, 503, 'limit_conn rejected');
 
-h2_settings($sess, 0, 0x4 => 2**16);
+$s->h2_settings(0, 0x4 => 2**16);
 
-h2_read($sess, all => [
+$s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 }
 ]);
 
 # limit_conn + client's RST_STREAM
 
-$sess = new_session();
-h2_settings($sess, 0, 0x4 => 1);
+$s = Test::Nginx::HTTP2->new();
+$s->h2_settings(0, 0x4 => 1);
 
-$sid = new_stream($sess, { path => '/t.html' });
-$frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
-h2_rst($sess, $sid, 5);
+$sid = $s->new_stream({ path => '/t.html' });
+$frames = $s->read(all => [{ sid => $sid, length => 1 }]);
+$s->h2_rst($sid, 5);
 
 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
 is($frame->{headers}->{':status'}, 200, 'RST_STREAM 1');
 
-$sid2 = new_stream($sess, { path => '/t.html' });
-$frames = h2_read($sess, all => [{ sid => $sid2, length => 1 }]);
+$sid2 = $s->new_stream({ path => '/t.html' });
+$frames = $s->read(all => [{ sid => $sid2, length => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
 is($frame->{headers}->{':status'}, 200, 'RST_STREAM 2');
--- a/h2_limit_req.t
+++ b/h2_limit_req.t
@@ -16,7 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -69,13 +69,13 @@ EOF
 $t->run();
 
 ###############################################################################
-
+my $sess;
 # request body delayed in limit_req
 
-my $sess = new_session();
-my $sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 });
-h2_body($sess, 'TEST');
-my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+my $s = Test::Nginx::HTTP2->new();
+my $sid = $s->new_stream({ path => '/proxy_limit_req/', body_more => 1 });
+$s->h2_body('TEST');
+my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
@@ -84,11 +84,11 @@ is(read_body_file($frame->{headers}->{'x
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.15');
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/proxy_limit_req/', body_more => 1 });
 select undef, undef, undef, 1.1;
-h2_body($sess, 'TEST');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TEST');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
@@ -99,28 +99,28 @@ is(read_body_file($frame->{headers}->{'x
 # request body delayed in limit_req - with an empty DATA frame
 # "zero size buf in output" alerts seen
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 });
-h2_body($sess, '');
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/proxy_limit_req/', body_more => 1 });
+$s->h2_body('');
 select undef, undef, undef, 1.1;
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'request body - limit req - empty');
 
 # predict send windows
 
-$sid = new_stream($sess);
-my ($maxwin) = sort {$a <=> $b} $sess->{streams}{$sid}, $sess->{conn_window};
+$sid = $s->new_stream();
+my ($maxwin) = sort {$a <=> $b} $s->{streams}{$sid}, $s->{conn_window};
 
 SKIP: {
 skip 'leaves coredump', 1 unless $t->has_version('1.9.7');
 skip 'not enough window', 1 if $maxwin < 5;
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy_limit_req/', body => 'TEST2' });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/proxy_limit_req/', body => 'TEST2' });
 select undef, undef, undef, 1.1;
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST2',
@@ -131,7 +131,7 @@ is(read_body_file($frame->{headers}->{'x
 # partial request body data frame received (to be discarded) within request
 # delayed in limit_req, the rest of data frame is received after response
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
 SKIP: {
 skip 'not enough window', 1 if $maxwin < 4;
@@ -140,9 +140,9 @@ TODO: {
 todo_skip 'use-after-free', 1 unless $ENV{TEST_NGINX_UNSAFE}
 	or $t->has_version('1.9.12');
 
-$sid = new_stream($sess, { path => '/limit_req', body => 'TEST', split => [61],
+$sid = $s->new_stream({ path => '/limit_req', body => 'TEST', split => [61],
 	split_delay => 1.1 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, '200', 'discard body - limit req - limited');
@@ -151,8 +151,8 @@ is($frame->{headers}->{':status'}, '200'
 
 }
 
-$sid = new_stream($sess, { path => '/' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream({ path => '/' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, '200', 'discard body - limit req - next');
@@ -167,12 +167,12 @@ TODO: {
 todo_skip 'use-after-free', 1 unless $ENV{TEST_NGINX_UNSAFE}
 	or $t->has_version('1.9.12');
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/limit_req', body => 'TEST', split => [61],
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/limit_req', body => 'TEST', split => [61],
 	abort => 1 });
 
 select undef, undef, undef, 1.1;
-close $sess->{socket};
+close $s->{socket};
 
 pass('discard body - limit req - eof');
 
--- a/h2_priority.t
+++ b/h2_priority.t
@@ -16,7 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -57,21 +57,21 @@ EOF
 
 # stream muliplexing + PRIORITY frames
 
-my $sess = new_session();
-my $sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+my $s = Test::Nginx::HTTP2->new();
+my $sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-my $sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+my $sid2 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-h2_priority($sess, 0, $sid);
-h2_priority($sess, 255, $sid2);
+$s->h2_priority(0, $sid);
+$s->h2_priority(255, $sid2);
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-my $frames = h2_read($sess, all => [
+my $frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 }
 ]);
@@ -81,21 +81,21 @@ is(join(' ', map { $_->{sid} } @data), "
 
 # and vice versa
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-h2_priority($sess, 255, $sid);
-h2_priority($sess, 0, $sid2);
+$s->h2_priority(255, $sid);
+$s->h2_priority(0, $sid2);
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 }
 ]);
@@ -105,18 +105,18 @@ is(join(' ', map { $_->{sid} } @data), "
 
 # stream muliplexing + HEADERS PRIORITY flag
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html', prio => 0 });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/t1.html', prio => 0 });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html', prio => 255 });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html', prio => 255 });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 }
 ]);
@@ -127,18 +127,18 @@ is($sids, "$sid2 $sid", 'weight - HEADER
 
 # and vice versa
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html', prio => 255 });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/t1.html', prio => 255 });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html', prio => 0 });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html', prio => 0 });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 }
 ]);
@@ -151,22 +151,22 @@ is($sids, "$sid $sid2", 'weight - HEADER
 
 # PRIORITY frame
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
-h2_priority($sess, 16, 3, 0);
-h2_priority($sess, 16, 1, 3);
+$s->h2_priority(16, 3, 0);
+$s->h2_priority(16, 1, 3);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 },
 ]);
@@ -177,22 +177,22 @@ is($sids, "$sid2 $sid", 'dependency - PR
 
 # and vice versa
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 1);
+$s->h2_priority(16, 1, 0);
+$s->h2_priority(16, 3, 1);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 },
 ]);
@@ -207,12 +207,12 @@ is($sids, "$sid $sid2", 'dependency - PR
 #   A stream cannot depend on itself.  An endpoint MUST treat this as a
 #   stream error of type PROTOCOL_ERROR.
 
-$sess = new_session();
-$sid = new_stream($sess);
-h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream();
+$s->read(all => [{ sid => $sid, fin => 1 }]);
 
-h2_priority($sess, 0, $sid, $sid);
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$s->h2_priority(0, $sid, $sid);
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 my ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
 is($frame->{sid}, $sid, 'dependency - PRIORITY self - RST_STREAM');
@@ -220,22 +220,22 @@ is($frame->{code}, 1, 'dependency - PRIO
 
 # HEADERS PRIORITY flag, reprioritize prior PRIORITY frame records
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 0);
+$s->h2_priority(16, 1, 0);
+$s->h2_priority(16, 3, 0);
 
-$sid = new_stream($sess, { path => '/t1.html', dep => 3 });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html', dep => 3 });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 },
 ]);
@@ -246,22 +246,22 @@ is($sids, "$sid2 $sid", 'dependency - HE
 
 # and vice versa
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 0);
+$s->h2_priority(16, 1, 0);
+$s->h2_priority(16, 3, 0);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html', dep => 1 });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html', dep => 1 });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
+$s->h2_window(2**17, $sid);
+$s->h2_window(2**17, $sid2);
+$s->h2_window(2**17);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 },
 ]);
@@ -272,9 +272,9 @@ is($sids, "$sid $sid2", 'dependency - HE
 
 # HEADERS - self dependency
 
-$sess = new_session();
-$sid = new_stream($sess, { dep => 1 });
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ dep => 1 });
+$frames = $s->read(all => [{ type => 'RST_STREAM' }]);
 
 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
 is($frame->{sid}, $sid, 'dependency - HEADERS self - RST_STREAM');
@@ -282,27 +282,27 @@ is($frame->{code}, 1, 'dependency - HEAD
 
 # PRIORITY frame, weighted dependencies
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
-h2_priority($sess, 16, 5, 0);
-h2_priority($sess, 255, 1, 5);
-h2_priority($sess, 0, 3, 5);
+$s->h2_priority(16, 5, 0);
+$s->h2_priority(255, 1, 5);
+$s->h2_priority(0, 3, 5);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-my $sid3 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid3, fin => 0x4 }]);
+my $sid3 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid3, fin => 0x4 }]);
 
-h2_window($sess, 2**16, 1);
-h2_window($sess, 2**16, 3);
-h2_window($sess, 2**16, 5);
-h2_window($sess, 2**16);
+$s->h2_window(2**16, 1);
+$s->h2_window(2**16, 3);
+$s->h2_window(2**16, 5);
+$s->h2_window(2**16);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 },
 	{ sid => $sid3, fin => 1 },
@@ -314,27 +314,27 @@ is($sids, "$sid3 $sid $sid2", 'weighted 
 
 # and vice versa
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
-h2_priority($sess, 16, 5, 0);
-h2_priority($sess, 0, 1, 5);
-h2_priority($sess, 255, 3, 5);
+$s->h2_priority(16, 5, 0);
+$s->h2_priority(0, 1, 5);
+$s->h2_priority(255, 3, 5);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+$sid2 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid2, fin => 0x4 }]);
 
-$sid3 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid3, fin => 0x4 }]);
+$sid3 = $s->new_stream({ path => '/t2.html' });
+$s->read(all => [{ sid => $sid3, fin => 0x4 }]);
 
-h2_window($sess, 2**16, 1);
-h2_window($sess, 2**16, 3);
-h2_window($sess, 2**16, 5);
-h2_window($sess, 2**16);
+$s->h2_window(2**16, 1);
+$s->h2_window(2**16, 3);
+$s->h2_window(2**16, 5);
+$s->h2_window(2**16);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid2, fin => 1 },
 	{ sid => $sid3, fin => 1 },
@@ -348,31 +348,31 @@ is($sids, "$sid3 $sid2 $sid", 'weighted 
 # initial dependency tree:
 # 1 <- [3] <- 5
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
-h2_window($sess, 2**18);
+$s->h2_window(2**18);
 
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 1);
-h2_priority($sess, 16, 5, 3);
+$s->h2_priority(16, 1, 0);
+$s->h2_priority(16, 3, 1);
+$s->h2_priority(16, 5, 3);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid2, length => 2**16 - 1 }]);
+$sid2 = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid2, length => 2**16 - 1 }]);
 
-$sid3 = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid3, length => 2**16 - 1 }]);
+$sid3 = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid3, length => 2**16 - 1 }]);
 
-h2_window($sess, 2**16, $sid2);
+$s->h2_window(2**16, $sid2);
 
-$frames = h2_read($sess, all => [{ sid => $sid2, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid2, fin => 1 }]);
 $sids = join ' ', map { $_->{sid} } grep { $_->{type} eq "DATA" } @$frames;
 is($sids, $sid2, 'removed dependency');
 
 for (1 .. 40) {
-	h2_read($sess, all => [{ sid => new_stream($sess), fin => 1 }]);
+	$s->read(all => [{ sid => $s->new_stream(), fin => 1 }]);
 }
 
 # make circular dependency
@@ -380,13 +380,13 @@ for (1 .. 40) {
 # 5 <- 1
 # 1 <- 5
 
-h2_priority($sess, 16, 1, 5);
-h2_priority($sess, 16, 5, 1);
+$s->h2_priority(16, 1, 5);
+$s->h2_priority(16, 5, 1);
 
-h2_window($sess, 2**16, $sid);
-h2_window($sess, 2**16, $sid3);
+$s->h2_window(2**16, $sid);
+$s->h2_window(2**16, $sid3);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid, fin => 1 },
 	{ sid => $sid3, fin => 1 },
 ]);
@@ -400,26 +400,26 @@ is($frame->{length}, 81, 'removed depend
 # PRIORITY - reprioritization with circular dependency - exclusive [5]
 # 1 <- [5] <- 3
 
-$sess = new_session();
+$s = Test::Nginx::HTTP2->new();
 
-h2_window($sess, 2**18);
+$s->h2_window(2**18);
 
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 1);
-h2_priority($sess, 16, 5, 1, excl => 1);
+$s->h2_priority(16, 1, 0);
+$s->h2_priority(16, 3, 1);
+$s->h2_priority(16, 5, 1, excl => 1);
 
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+$sid = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid, length => 2**16 - 1 }]);
 
-$sid2 = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid2, length => 2**16 - 1 }]);
+$sid2 = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid2, length => 2**16 - 1 }]);
 
-$sid3 = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid3, length => 2**16 - 1 }]);
+$sid3 = $s->new_stream({ path => '/t1.html' });
+$s->read(all => [{ sid => $sid3, length => 2**16 - 1 }]);
 
-h2_window($sess, 2**16, $sid);
+$s->h2_window(2**16, $sid);
 
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 $sids = join ' ', map { $_->{sid} } grep { $_->{type} eq "DATA" } @$frames;
 is($sids, $sid, 'exclusive dependency - parent removed');
 
@@ -427,12 +427,12 @@ is($sids, $sid, 'exclusive dependency - 
 # 5 <- 3 -- current dependency tree before reprioritization
 # 3 <- 5
 
-h2_priority($sess, 16, 5, 3);
+$s->h2_priority(16, 5, 3);
 
-h2_window($sess, 2**16, $sid2);
-h2_window($sess, 2**16, $sid3);
+$s->h2_window(2**16, $sid2);
+$s->h2_window(2**16, $sid3);
 
-$frames = h2_read($sess, all => [
+$frames = $s->read(all => [
 	{ sid => $sid2, fin => 1 },
 	{ sid => $sid3, fin => 1 },
 ]);
--- a/h2_proxy_protocol.t
+++ b/h2_proxy_protocol.t
@@ -59,9 +59,9 @@ EOF
 ###############################################################################
 
 my $proxy = 'PROXY TCP4 192.0.2.1 192.0.2.2 1234 5678' . CRLF;
-my $sess = new_session(8081, proxy => $proxy);
-my $sid = new_stream($sess, { path => '/pp' });
-my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+my $s = Test::Nginx::HTTP2->new(8081, proxy => $proxy);
+my $sid = $s->new_stream({ path => '/pp' });
+my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 ok($frame, 'PROXY HEADERS frame');
@@ -70,8 +70,8 @@ is($frame->{headers}->{'x-pp'}, '192.0.2
 # invalid PROXY protocol string
 
 $proxy = 'BOGUS TCP4 192.0.2.1 192.0.2.2 1234 5678' . CRLF;
-$sess = new_session(8081, preface => $proxy, pure => 1);
-$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
+$s = Test::Nginx::HTTP2->new(8081, preface => $proxy, pure => 1);
+$frames = $s->read(all => [{ type => 'GOAWAY' }]);
 
 ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
 ok($frame, 'invalid PROXY - GOAWAY frame');
--- a/h2_proxy_request_buffering.t
+++ b/h2_proxy_request_buffering.t
@@ -18,7 +18,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame :io /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -176,9 +176,9 @@ sub get_body {
 	)
 		or die "Can't create listening socket: $!\n";
 
-	my $sess = new_session(8080);
+	my $s = Test::Nginx::HTTP2->new();
 	my $sid = exists $extra{'content-length'}
-		? new_stream($sess, { headers => [
+		? $s->new_stream({ headers => [
 			{ name => ':method', value => 'GET' },
 			{ name => ':scheme', value => 'http' },
 			{ name => ':path', value => $url, },
@@ -186,39 +186,37 @@ sub get_body {
 			{ name => 'content-length',
 				value => $extra{'content-length'} }],
 			body_more => 1 })
-		: new_stream($sess, { path => $url, body_more => 1 });
+		: $s->new_stream({ path => $url, body_more => 1 });
 
 	$client = $server->accept() or return;
 
 	log2c("(new connection $client)");
 
-	$f->{headers} = raw_read($client, '', 1, \&log2i);
+	$f->{headers} = backend_read($client);
 
 	my $chunked = $f->{headers} =~ /chunked/;
 
-	my $body_read = sub {
-		my ($s, $buf, $len, $wait) = @_;
+	$f->{upload} = sub {
+		my ($body, %extra) = @_;
+		my $len = length($body);
+		my $wait = $extra{wait};
+
+		$s->h2_body($body, { %extra });
+
+		$body = '';
 
 		for (1 .. 10) {
-			$buf = raw_read($s, $buf, length($buf) + 1, \&log2i,
-				$wait) or return '';
+			my $buf = backend_read($client, $wait) or return '';
+			$body .= $buf;
 
 			my $got = 0;
 			$got += $chunked ? hex $_ : $_ for $chunked
-				? $buf =~ /(\w+)\x0d\x0a?\w+\x0d\x0a?/g
-				: length($buf);
+				? $body =~ /(\w+)\x0d\x0a?\w+\x0d\x0a?/g
+				: length($body);
 			last if $got >= $len;
 		}
 
-		return $buf;
-	};
-
-	$f->{upload} = sub {
-		my ($body, %extra) = @_;
-
-		h2_body($sess, $body, { %extra });
-
-		return $body_read->($client, '', length($body), $extra{wait});
+		return $body;
 	};
 	$f->{http_end} = sub {
 		$client->write(<<EOF);
@@ -229,13 +227,24 @@ EOF
 
 		$client->close;
 
-		my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+		my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 		my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 		return $frame->{headers}->{':status'};
 	};
 	return $f;
 }
 
+sub backend_read {
+	my ($s, $timo) = @_;
+	my $buf = '';
+
+	if (IO::Select->new($s)->can_read($timo || 3)) {
+		$s->sysread($buf, 16384) or return;
+		log2i($buf);
+	}
+	return $buf;
+}
+
 sub log2i { Test::Nginx::log_core('|| <<', @_); }
 sub log2o { Test::Nginx::log_core('|| >>', @_); }
 sub log2c { Test::Nginx::log_core('||', @_); }
--- a/h2_proxy_request_buffering_ssl.t
+++ b/h2_proxy_request_buffering_ssl.t
@@ -18,7 +18,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame :io /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -201,9 +201,9 @@ sub get_body {
 	)
 		or die "Can't create listening socket: $!\n";
 
-	my $sess = new_session(8080);
+	my $s = Test::Nginx::HTTP2->new();
 	my $sid = exists $extra{'content-length'}
-		? new_stream($sess, { headers => [
+		? $s->new_stream({ headers => [
 			{ name => ':method', value => 'GET' },
 			{ name => ':scheme', value => 'http' },
 			{ name => ':path', value => $url, },
@@ -211,39 +211,37 @@ sub get_body {
 			{ name => 'content-length',
 				value => $extra{'content-length'} }],
 			body_more => 1 })
-		: new_stream($sess, { path => $url, body_more => 1 });
+		: $s->new_stream({ path => $url, body_more => 1 });
 
 	$client = $server->accept() or return;
 
 	log2c("(new connection $client)");
 
-	$f->{headers} = raw_read($client, '', 1, \&log2i);
+	$f->{headers} = backend_read($client);
 
 	my $chunked = $f->{headers} =~ /chunked/;
 
-	my $body_read = sub {
-		my ($s, $buf, $len, $wait) = @_;
+	$f->{upload} = sub {
+		my ($body, %extra) = @_;
+		my $len = length($body);
+		my $wait = $extra{wait};
+
+		$s->h2_body($body, { %extra });
+
+		$body = '';
 
 		for (1 .. 10) {
-			$buf = raw_read($s, $buf, length($buf) + 1, \&log2i,
-				$wait) or return '';
+			my $buf = backend_read($client, $wait) or return '';
+			$body .= $buf;
 
 			my $got = 0;
 			$got += $chunked ? hex $_ : $_ for $chunked
-				? $buf =~ /(\w+)\x0d\x0a?\w+\x0d\x0a?/g
-				: length($buf);
+				? $body =~ /(\w+)\x0d\x0a?\w+\x0d\x0a?/g
+				: length($body);
 			last if $got >= $len;
 		}
 
-		return $buf;
-	};
-
-	$f->{upload} = sub {
-		my ($body, %extra) = @_;
-
-		h2_body($sess, $body, { %extra });
-
-		return $body_read->($client, '', length($body), $extra{wait});
+		return $body;
 	};
 	$f->{http_end} = sub {
 		$client->write(<<EOF);
@@ -254,13 +252,24 @@ EOF
 
 		$client->close;
 
-		my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+		my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 		my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 		return $frame->{headers}->{':status'};
 	};
 	return $f;
 }
 
+sub backend_read {
+	my ($s, $timo) = @_;
+	my $buf = '';
+
+	if (IO::Select->new($s)->can_read($timo || 3)) {
+		$s->sysread($buf, 16384) or return;
+		log2i($buf);
+	}
+	return $buf;
+}
+
 sub log2i { Test::Nginx::log_core('|| <<', @_); }
 sub log2o { Test::Nginx::log_core('|| >>', @_); }
 sub log2c { Test::Nginx::log_core('||', @_); }
--- a/h2_proxy_ssl.t
+++ b/h2_proxy_ssl.t
@@ -16,7 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -86,10 +86,10 @@ foreach my $name ('localhost') {
 TODO: {
 local $TODO = 'not yet' unless $t->has_version('1.9.14');
 
-my $sess = new_session();
-my $sid = new_stream($sess, { path => '/proxy_ssl/', body_more => 1 });
-h2_body($sess, '');
-my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+my $s = Test::Nginx::HTTP2->new();
+my $sid = $s->new_stream({ path => '/proxy_ssl/', body_more => 1 });
+$s->h2_body('');
+my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'empty request body');
--- a/h2_request_body.t
+++ b/h2_request_body.t
@@ -16,7 +16,7 @@ BEGIN { use FindBin; chdir($FindBin::Bin
 
 use lib 'lib';
 use Test::Nginx;
-use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+use Test::Nginx::HTTP2;
 
 ###############################################################################
 
@@ -72,37 +72,37 @@ EOF
 
 # request body (uses proxied response)
 
-my $sess = new_session();
-my $sid = new_stream($sess, { path => '/proxy2/t.html', body_more => 1 });
-h2_body($sess, 'TEST');
-my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+my $s = Test::Nginx::HTTP2->new();
+my $sid = $s->new_stream({ path => '/proxy2/t.html', body_more => 1 });
+$s->h2_body('TEST');
+my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST', 'request body');
 
 # request body with padding (uses proxied response)
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/t.html', body_more => 1 });
-h2_body($sess, 'TEST', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/proxy2/t.html', body_more => 1 });
+$s->h2_body('TEST', { body_padding => 42 });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
 	'request body with padding');
 
-$sid = new_stream($sess);
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sid = $s->new_stream();
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, '200', 'request body with padding - next');
 
 # request body sent in multiple DATA frames in a single packet
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/t.html', body_more => 1 });
-h2_body($sess, 'TEST', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/proxy2/t.html', body_more => 1 });
+$s->h2_body('TEST', { body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
@@ -110,12 +110,12 @@ is(read_body_file($frame->{headers}->{'x
 
 # request body sent in multiple DATA frames, each in its own packet
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/t.html', body_more => 1 });
-h2_body($sess, 'TEST', { body_more => 1 });
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/proxy2/t.html', body_more => 1 });
+$s->h2_body('TEST', { body_more => 1 });
 select undef, undef, undef, 0.1;
-h2_body($sess, 'MOREDATA');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('MOREDATA');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTMOREDATA',
@@ -124,10 +124,10 @@ is(read_body_file($frame->{headers}->{'x
 # request body with an empty DATA frame
 # "zero size buf in output" alerts seen
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/', body_more => 1 });
-h2_body($sess, '');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/proxy2/', body_more => 1 });
+$s->h2_body('');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'request body - empty');
@@ -153,9 +153,9 @@ is(read_body_file($frame->{headers}{'x-b
 TODO: {
 local $TODO = 'not yet';
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1 });
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }], wait => 0.5);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1 });
+$frames = $s->read(all => [{ type => 'RST_STREAM' }], wait => 0.5);
 
 ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
 is($frame->{code}, 0, 'request body discarded - zero RST_STREAM');
@@ -164,40 +164,38 @@ is($frame->{code}, 0, 'request body disc
 
 # malformed request body length not equal to content-length
 
-$sess = new_session();
-$sid = new_stream($sess,
-	{ body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/client_max_body_size', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'content-length', value => '5', mode => 1 }]});
-h2_body($sess, 'TEST');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TEST');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 400, 'request body less than content-length');
 
-$sid = new_stream($sess,
-	{ body_more => 1, headers => [
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 0 },
 	{ name => ':scheme', value => 'http', mode => 0 },
 	{ name => ':path', value => '/client_max_body_size', mode => 1 },
 	{ name => ':authority', value => 'localhost', mode => 1 },
 	{ name => 'content-length', value => '3', mode => 1 }]});
-h2_body($sess, 'TEST');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TEST');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 400, 'request body more than content-length');
 
 # client_max_body_size
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/client_max_body_size/t.html',
 	body_more => 1 });
-h2_body($sess, 'TESTTEST12');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST12');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'client_max_body_size - status');
@@ -206,22 +204,22 @@ is(read_body_file($frame->{headers}->{'x
 
 # client_max_body_size - limited
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/client_max_body_size/t.html',
 	body_more => 1 });
-h2_body($sess, 'TESTTEST123');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST123');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 413, 'client_max_body_size - limited');
 
 # client_max_body_size - many DATA frames
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/client_max_body_size/t.html',
 	body_more => 1 });
-h2_body($sess, 'TESTTEST12', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST12', { body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'client_max_body_size many - status');
@@ -230,22 +228,22 @@ is(read_body_file($frame->{headers}->{'x
 
 # client_max_body_size - many DATA frames - limited
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/client_max_body_size/t.html',
 	body_more => 1 });
-h2_body($sess, 'TESTTEST123', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST123', { body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 413, 'client_max_body_size many - limited');
 
 # client_max_body_size - padded DATA
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/client_max_body_size/t.html',
 	body_more => 1 });
-h2_body($sess, 'TESTTEST12', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST12', { body_padding => 42 });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200, 'client_max_body_size pad - status');
@@ -254,22 +252,22 @@ is(read_body_file($frame->{headers}->{'x
 
 # client_max_body_size - padded DATA - limited
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/client_max_body_size/t.html',
 	body_more => 1 });
-h2_body($sess, 'TESTTEST123', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST123', { body_padding => 42 });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 413, 'client_max_body_size pad - limited');
 
 # client_max_body_size - many padded DATA frames
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/client_max_body_size/t.html',
 	body_more => 1 });
-h2_body($sess, 'TESTTEST12', { body_padding => 42, body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST12', { body_padding => 42, body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -279,11 +277,11 @@ is(read_body_file($frame->{headers}->{'x
 
 # client_max_body_size - many padded DATA frames - limited
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/client_max_body_size/t.html',
 	body_more => 1 });
-h2_body($sess, 'TESTTEST123', { body_padding => 42, body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST123', { body_padding => 42, body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 413,
@@ -291,14 +289,14 @@ is($frame->{headers}->{':status'}, 413,
 
 # request body without content-length
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 2 },
 	{ name => ':scheme', value => 'http', mode => 2 },
 	{ name => ':path', value => '/client_max_body_size', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST12');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST12');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -308,14 +306,14 @@ is(read_body_file($frame->{headers}->{'x
 
 # request body without content-length - limited
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 2 },
 	{ name => ':scheme', value => 'http', mode => 2 },
 	{ name => ':path', value => '/client_max_body_size', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST123');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST123');
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 413,
@@ -323,14 +321,14 @@ is($frame->{headers}->{':status'}, 413,
 
 # request body without content-length - many DATA frames
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 2 },
 	{ name => ':scheme', value => 'http', mode => 2 },
 	{ name => ':path', value => '/client_max_body_size', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST12', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST12', { body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -340,14 +338,14 @@ is(read_body_file($frame->{headers}->{'x
 
 # request body without content-length - many DATA frames - limited
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 2 },
 	{ name => ':scheme', value => 'http', mode => 2 },
 	{ name => ':path', value => '/client_max_body_size', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST123', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST123', { body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 413,
@@ -355,14 +353,14 @@ is($frame->{headers}->{':status'}, 413,
 
 # request body without content-length - padding
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 2 },
 	{ name => ':scheme', value => 'http', mode => 2 },
 	{ name => ':path', value => '/client_max_body_size', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST12', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST12', { body_padding => 42 });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -372,14 +370,14 @@ is(read_body_file($frame->{headers}->{'x
 
 # request body without content-length - padding - limited
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 2 },
 	{ name => ':scheme', value => 'http', mode => 2 },
 	{ name => ':path', value => '/client_max_body_size', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST123', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST123', { body_padding => 42 });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 413,
@@ -387,14 +385,14 @@ is($frame->{headers}->{':status'}, 413,
 
 # request body without content-length - padding with many DATA frames
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 2 },
 	{ name => ':scheme', value => 'http', mode => 2 },
 	{ name => ':path', value => '/client_max_body_size', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST', { body_padding => 42, body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST', { body_padding => 42, body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 200,
@@ -404,14 +402,14 @@ is(read_body_file($frame->{headers}->{'x
 
 # request body without content-length - padding with many DATA frames - limited
 
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ body_more => 1, headers => [
 	{ name => ':method', value => 'GET', mode => 2 },
 	{ name => ':scheme', value => 'http', mode => 2 },
 	{ name => ':path', value => '/client_max_body_size', mode => 2 },
 	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST123', { body_padding => 42, body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s->h2_body('TESTTEST123', { body_padding => 42, body_split => [2] });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{':status'}, 413,
--- a/h2_server_tokens.t
+++ b/h2_server_tokens.t
@@ -101,9 +101,9 @@ like(body('/on/404'), qr/nginx\/\d+\.\d+
 sub header_server {
 	my ($path) = shift;
 
-	my $sess = new_session();
-	my $sid = new_stream($sess, { path => $path });
-	my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+	my $s = Test::Nginx::HTTP2->new();
+	my $sid = $s->new_stream({ path => $path });
+	my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 	my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 	return $frame->{headers}->{'server'};
@@ -112,9 +112,9 @@ sub header_server {
 sub body {
 	my ($path) = shift;
 
-	my $sess = new_session();
-	my $sid = new_stream($sess, { path => $path });
-	my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+	my $s = Test::Nginx::HTTP2->new();
+	my $sid = $s->new_stream({ path => $path });
+	my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 	my ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 	return $frame->{'data'};
--- a/h2_ssl.t
+++ b/h2_ssl.t
@@ -91,7 +91,7 @@ open STDERR, ">&", \*OLDERR;
 
 ###############################################################################
 
-my ($sess, $sid, $frames, $frame);
+my ($s, $sid, $frames, $frame);
 
 # SSL/TLS connection, NPN
 
@@ -99,9 +99,9 @@ SKIP: {
 eval { IO::Socket::SSL->can_npn() or die; };
 skip 'OpenSSL NPN support required', 1 if $@;
 
-$sess = new_session(8081, SSL => 1, npn => 'h2');
-$sid = new_stream($sess, { path => '/h2' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new(8081, SSL => 1, npn => 'h2');
+$sid = $s->new_stream({ path => '/h2' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'h2', 'http variable - npn');
@@ -114,9 +114,9 @@ SKIP: {
 eval { IO::Socket::SSL->can_alpn() or die; };
 skip 'OpenSSL ALPN support required', 1 if $@;
 
-$sess = new_session(8081, SSL => 1, alpn => 'h2');
-$sid = new_stream($sess, { path => '/h2' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new(8081, SSL => 1, alpn => 'h2');
+$sid = $s->new_stream({ path => '/h2' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'h2', 'http variable - alpn');
@@ -129,9 +129,9 @@ SKIP: {
 eval { IO::Socket::SSL->can_npn() or die; };
 skip 'OpenSSL NPN support required', 1 if $@;
 
-$sess = new_session(8081, SSL => 1, npn => 'h2');
-$sid = new_stream($sess, { path => '/sp' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new(8081, SSL => 1, npn => 'h2');
+$sid = $s->new_stream({ path => '/sp' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'HTTP/2.0', 'server_protocol variable - npn');
@@ -144,9 +144,9 @@ SKIP: {
 eval { IO::Socket::SSL->can_alpn() or die; };
 skip 'OpenSSL ALPN support required', 1 if $@;
 
-$sess = new_session(8081, SSL => 1, alpn => 'h2');
-$sid = new_stream($sess, { path => '/sp' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new(8081, SSL => 1, alpn => 'h2');
+$sid = $s->new_stream({ path => '/sp' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'HTTP/2.0', 'server_protocol variable - alpn');
@@ -159,9 +159,9 @@ SKIP: {
 eval { IO::Socket::SSL->can_npn() or die; };
 skip 'OpenSSL NPN support required', 1 if $@;
 
-$sess = new_session(8081, SSL => 1, npn => 'h2');
-$sid = new_stream($sess, { path => '/scheme' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new(8081, SSL => 1, npn => 'h2');
+$sid = $s->new_stream({ path => '/scheme' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'https', 'scheme variable - npn');
@@ -174,9 +174,9 @@ SKIP: {
 eval { IO::Socket::SSL->can_alpn() or die; };
 skip 'OpenSSL ALPN support required', 1 if $@;
 
-$sess = new_session(8081, SSL => 1, alpn => 'h2');
-$sid = new_stream($sess, { path => '/scheme' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new(8081, SSL => 1, alpn => 'h2');
+$sid = $s->new_stream({ path => '/scheme' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'https', 'scheme variable - alpn');
@@ -189,9 +189,9 @@ SKIP: {
 eval { IO::Socket::SSL->can_npn() or die; };
 skip 'OpenSSL NPN support required', 1 if $@;
 
-$sess = new_session(8081, SSL => 1, npn => 'h2');
-$sid = new_stream($sess, { path => '/https' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new(8081, SSL => 1, npn => 'h2');
+$sid = $s->new_stream({ path => '/https' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'on', 'https variable - npn');
@@ -204,9 +204,9 @@ SKIP: {
 eval { IO::Socket::SSL->can_alpn() or die; };
 skip 'OpenSSL ALPN support required', 1 if $@;
 
-$sess = new_session(8081, SSL => 1, alpn => 'h2');
-$sid = new_stream($sess, { path => '/https' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new(8081, SSL => 1, alpn => 'h2');
+$sid = $s->new_stream({ path => '/https' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'on', 'https variable - alpn');
--- a/h2_ssl_verify_client.t
+++ b/h2_ssl_verify_client.t
@@ -139,13 +139,13 @@ sub get {
 		return undef;
 	}
 
-	my $sess = new_session(8443, socket => $s);
-	my $sid = new_stream($sess,  { headers => [
+	my $sess = Test::Nginx::HTTP2->new(8443, socket => $s);
+	my $sid = $sess->new_stream({ headers => [
 		{ name => ':method', value => 'GET', mode => 0 },
 		{ name => ':scheme', value => 'http', mode => 0 },
 		{ name => ':path', value => '/t', mode => 1 },
 		{ name => ':authority', value => $host, mode => 1 }]});
-	my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+	my $frames = $sess->read(all => [{ sid => $sid, fin => 1 }]);
 
 	my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 	return $frame->{'headers'};
--- a/h2_variables.t
+++ b/h2_variables.t
@@ -63,36 +63,36 @@ EOF
 
 # $http2
 
-my $sess = new_session();
-my $sid = new_stream($sess, { path => '/h2' });
-my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+my $s = Test::Nginx::HTTP2->new();
+my $sid = $s->new_stream({ path => '/h2' });
+my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 my ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'h2c', 'http variable - h2c');
 
 # $server_protocol
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/sp' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/sp' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'HTTP/2.0', 'server_protocol variable');
 
 # $scheme
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/scheme' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/scheme' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, 'http', 'scheme variable');
 
 # $https
 
-$sess = new_session();
-$sid = new_stream($sess, { path => '/https' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$s = Test::Nginx::HTTP2->new();
+$sid = $s->new_stream({ path => '/https' });
+$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
 
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame->{data}, '', 'https variable');
--- a/lib/Test/Nginx/HTTP2.pm
+++ b/lib/Test/Nginx/HTTP2.pm
@@ -10,15 +10,6 @@ package Test::Nginx::HTTP2;
 use warnings;
 use strict;
 
-use base qw/ Exporter /;
-our @EXPORT = qw/ new_session new_stream h2_read /;
-our %EXPORT_TAGS = (
-	io => [ qw/ raw_write raw_read / ],
-	frame => [ qw/ h2_ping h2_rst h2_goaway h2_priority h2_window
-		h2_settings h2_unknown h2_continue h2_body/ ]
-);
-our @EXPORT_OK = ( @{ $EXPORT_TAGS{'io'} }, @{ $EXPORT_TAGS{'frame'} } );
-
 use Test::More qw//;
 use IO::Select;
 use IO::Socket;
@@ -113,7 +104,7 @@ sub h2_body {
 	my $sid = $sess->{last_stream};
 
 	if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) {
-		h2_read($sess, all => [{ type => 'WINDOW_UPDATE' }]);
+		$sess->read(all => [{ type => 'WINDOW_UPDATE' }]);
 	}
 
 	if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) {
@@ -260,7 +251,7 @@ done:
 	return $ctx->{last_stream};
 }
 
-sub h2_read {
+sub read {
 	my ($sess, %extra) = @_;
 	my (@got);
 	my $s = $sess->{socket};
@@ -438,7 +429,8 @@ sub raw_write {
 	}
 }
 
-sub new_session {
+sub new {
+	my $class = shift;
 	my ($port, %extra) = @_;
 
 	my $s = $extra{socket} || new_socket($port, %extra);
@@ -458,12 +450,13 @@ sub new_session {
 		dynamic_decode => [ static_table() ],
 		static_table_size => scalar @{[static_table()]},
 		iws => 65535, conn_window => 65535, streams => {}};
+	bless $ctx, $class;
 
 	return $ctx if $extra{pure};
 
 	# update windows, if any
 
-	my $frames = h2_read($ctx, all => [
+	my $frames = $ctx->read(all => [
 		{ type => 'WINDOW_UPDATE' },
 		{ type => 'SETTINGS'}
 	]);