File Coverage

blib/lib/Net/HTTP2/nghttp2/Session.pm
Criterion Covered Total %
statement 72 84 85.7
branch 19 28 67.8
condition 22 33 66.6
subroutine 12 13 92.3
pod 7 7 100.0
total 132 165 80.0


line stmt bran cond sub pod time code
1             package Net::HTTP2::nghttp2::Session;
2              
3 14     14   7017 use strict;
  14         19  
  14         444  
4 14     14   59 use warnings;
  14         19  
  14         600  
5 14     14   56 use Carp qw(croak);
  14         42  
  14         648  
6 14     14   58 use Scalar::Util qw(weaken);
  14         18  
  14         550  
7 14     14   55 use Net::HTTP2::nghttp2; # XS bootstrap (loads _new_server_xs etc.)
  14         36  
  14         10887  
8              
9             # Session is implemented in XS, this is the Perl-side API wrapper
10              
11             sub new_server {
12 101     101 1 340327 my ($class, %args) = @_;
13              
14 101   50     328 my $callbacks = delete $args{callbacks} // {};
15 101         139 my $user_data = delete $args{user_data};
16 101   100     317 my $settings = delete $args{settings} // {};
17              
18             # Session options (passed to nghttp2_option / nghttp2_session_server_new2)
19 101         124 my $max_send_header_block_length = delete $args{max_send_header_block_length};
20 101         117 my $stream_reset_burst = delete $args{stream_reset_burst};
21 101         184 my $stream_reset_rate = delete $args{stream_reset_rate};
22              
23             # Validate required callbacks
24 101         190 for my $cb (qw(on_begin_headers on_header on_frame_recv)) {
25 303 50       558 croak "Missing required callback: $cb" unless $callbacks->{$cb};
26             }
27              
28             # Build options hash for XS if any session options are set
29 101         114 my %options;
30 101 100       163 $options{max_send_header_block_length} = $max_send_header_block_length
31             if defined $max_send_header_block_length;
32              
33             # Rapid Reset (CVE-2023-44487) RST_STREAM rate limit. burst and rate must be
34             # set together; they map to nghttp2_option_set_stream_reset_rate_limit.
35 101 100 100     290 if (defined $stream_reset_burst || defined $stream_reset_rate) {
36 3 100 100     283 croak "stream_reset_burst and stream_reset_rate must be set together"
37             unless defined $stream_reset_burst && defined $stream_reset_rate;
38 1         2 $options{stream_reset_burst} = $stream_reset_burst;
39 1         5 $options{stream_reset_rate} = $stream_reset_rate;
40             }
41              
42             # Create the session via XS
43 99 100       2073 my $self = %options
44             ? $class->_new_server_xs($callbacks, $user_data, \%options)
45             : $class->_new_server_xs($callbacks, $user_data);
46              
47             # Apply initial settings
48 99 100       248 if (%$settings) {
49 3         16 $self->submit_settings($settings);
50             }
51              
52 99         290 return $self;
53             }
54              
55             sub new_client {
56 22     22 1 55556 my ($class, %args) = @_;
57              
58 22   50     50 my $callbacks = delete $args{callbacks} // {};
59 22         32 my $user_data = delete $args{user_data};
60              
61 22         515 return $class->_new_client_xs($callbacks, $user_data);
62             }
63              
64             # High-level request submission (client-side)
65             # Body can be: undef (no body), string (static body), or CODE ref (streaming callback).
66             # Streaming callback receives ($stream_id, $max_length) and returns:
67             # ($data, $eof_flag) - send data, eof=1 closes stream
68             # undef - defer; call resume_stream() when data is ready
69             sub submit_request {
70 24     24 1 2491 my ($self, %args) = @_;
71              
72 24   100     75 my $method = delete $args{method} // 'GET';
73 24   100     75 my $path = delete $args{path} // '/';
74 24   100     47 my $scheme = delete $args{scheme} // 'https';
75 24         27 my $authority = delete $args{authority};
76 24   100     46 my $headers = delete $args{headers} // [];
77 24         43 my $body = delete $args{body};
78              
79             # Build pseudo-headers + regular headers
80 24         63 my @nv = (
81             [':method', $method],
82             [':path', $path],
83             [':scheme', $scheme],
84             );
85 24 100       51 push @nv, [':authority', $authority] if defined $authority;
86 24         43 push @nv, @$headers;
87              
88 24         197 return $self->_submit_request_xs(\@nv, $body);
89             }
90              
91             # Convenience method to send server connection preface (SETTINGS frame)
92             sub send_connection_preface {
93 120     120 1 4013 my ($self, %settings) = @_;
94              
95             # Default settings for server
96 120 100       407 %settings = (
97             max_concurrent_streams => 100,
98             initial_window_size => 65535,
99             %settings,
100             ) unless %settings;
101              
102 120         709 return $self->submit_settings(\%settings);
103             }
104              
105             # High-level response submission
106             sub submit_response {
107 2     2 1 1252 my ($self, $stream_id, %args) = @_;
108              
109 2   50     10 my $status = delete $args{status} // 200;
110 2   50     7 my $headers = delete $args{headers} // [];
111 2         3 my $body = delete $args{body};
112 2         5 my $data_cb = delete $args{data_callback};
113 2         4 my $cb_data = delete $args{callback_data};
114              
115             # Build pseudo-headers + regular headers
116 2         7 my @nv = (
117             [':status', $status],
118             @$headers,
119             );
120              
121 2 100 66     17 if (defined $body && !ref($body)) {
    50          
    0          
122             # Static body - convert to simple streaming callback
123 1         3 my $sent = 0;
124 1         3 my $body_bytes = $body;
125             $data_cb = sub {
126 1     1   19 my ($stream_id, $max_len) = @_;
127 1 50       4 return ('', 1) if $sent; # EOF
128 1         2 $sent = 1;
129 1         13 return ($body_bytes, 1); # data + EOF
130 1         6 };
131 1         15 return $self->_submit_response_streaming($stream_id, \@nv, $data_cb, undef);
132             }
133             elsif (ref($body) eq 'CODE') {
134             # CODE ref body - streaming callback data provider
135 1         10 return $self->_submit_response_streaming($stream_id, \@nv, $body, $cb_data);
136             }
137             elsif ($data_cb) {
138             # Dynamic body - use callback-based data provider
139 0         0 return $self->_submit_response_streaming($stream_id, \@nv, $data_cb, $cb_data);
140             }
141             else {
142             # No body (e.g., 204 No Content, redirects)
143 0         0 return $self->_submit_response_no_body($stream_id, \@nv);
144             }
145             }
146              
147             # Resume a deferred stream (call after data becomes available)
148             sub resume_stream {
149 2     2 1 2313 my ($self, $stream_id) = @_;
150 2         6 $self->_clear_deferred($stream_id);
151 2         7 return $self->resume_data($stream_id);
152             }
153              
154             # High-level push promise submission
155             sub submit_push_promise {
156 0     0 1   my ($self, $stream_id, %args) = @_;
157              
158 0   0       my $method = delete $args{method} // 'GET';
159 0 0         my $path = delete $args{path} or croak "path required for push promise";
160 0   0       my $scheme = delete $args{scheme} // 'https';
161 0           my $authority = delete $args{authority};
162 0   0       my $headers = delete $args{headers} // [];
163              
164 0           my @nv = (
165             [':method', $method],
166             [':path', $path],
167             [':scheme', $scheme],
168             );
169 0 0         push @nv, [':authority', $authority] if defined $authority;
170 0           push @nv, @$headers;
171              
172 0           return $self->_submit_push_promise_xs($stream_id, \@nv);
173             }
174              
175             1;
176              
177             __END__