File Coverage

blib/lib/Net/HTTP2/nghttp2/Session.pm
Criterion Covered Total %
statement 66 78 84.6
branch 15 24 62.5
condition 16 27 59.2
subroutine 12 13 92.3
pod 7 7 100.0
total 116 149 77.8


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