Newer Older
328 lines | 7.726kb
add files
Yuki Kimoto authored on 2014-03-26
1
package Mojo::Transaction::HTTP;
2
use Mojo::Base 'Mojo::Transaction';
3

            
4
use Mojo::Transaction::WebSocket;
5

            
6
has 'previous';
7

            
8
sub client_read {
9
  my ($self, $chunk) = @_;
10

            
11
  # Skip body for HEAD request
12
  my $res = $self->res;
13
  $res->content->skip_body(1) if uc $self->req->method eq 'HEAD';
14
  return unless $res->parse($chunk)->is_finished;
15

            
16
  # Unexpected 1xx response
17
  return $self->{state} = 'finished'
18
    if !$res->is_status_class(100) || $res->headers->upgrade;
19
  $self->res($res->new)->emit(unexpected => $res);
20
  return unless length(my $leftovers = $res->content->leftovers);
21
  $self->client_read($leftovers);
22
}
23

            
24
sub client_write { shift->_write(0) }
25

            
26
sub is_empty { !!(uc $_[0]->req->method eq 'HEAD' || $_[0]->res->is_empty) }
27

            
28
sub keep_alive {
29
  my $self = shift;
30

            
31
  # Close
32
  my $req      = $self->req;
33
  my $res      = $self->res;
34
  my $req_conn = lc($req->headers->connection // '');
35
  my $res_conn = lc($res->headers->connection // '');
36
  return undef if $req_conn eq 'close' || $res_conn eq 'close';
37

            
38
  # Keep-alive
39
  return 1 if $req_conn eq 'keep-alive' || $res_conn eq 'keep-alive';
40

            
41
  # No keep-alive for 1.0
42
  return !($req->version eq '1.0' || $res->version eq '1.0');
43
}
44

            
45
sub redirects {
46
  my $previous = shift;
47
  my @redirects;
48
  unshift @redirects, $previous while $previous = $previous->previous;
49
  return \@redirects;
50
}
51

            
52
sub server_read {
53
  my ($self, $chunk) = @_;
54

            
55
  # Parse request
56
  my $req = $self->req;
57
  $req->parse($chunk) unless $req->error;
58
  $self->{state} ||= 'read';
59

            
60
  # Generate response
61
  return unless $req->is_finished && !$self->{handled}++;
62
  $self->emit(upgrade => Mojo::Transaction::WebSocket->new(handshake => $self))
63
    if lc($req->headers->upgrade // '') eq 'websocket';
64
  $self->emit('request');
65
}
66

            
67
sub server_write { shift->_write(1) }
68

            
69
sub _body {
70
  my ($self, $msg, $finish) = @_;
71

            
72
  # Prepare body chunk
73
  my $buffer = $msg->get_body_chunk($self->{offset});
74
  my $written = defined $buffer ? length $buffer : 0;
75
  $self->{write} = $msg->content->is_dynamic ? 1 : ($self->{write} - $written);
76
  $self->{offset} = $self->{offset} + $written;
77
  if (defined $buffer) { delete $self->{delay} }
78

            
79
  # Delayed
80
  else {
81
    if   (delete $self->{delay}) { $self->{state} = 'paused' }
82
    else                         { $self->{delay} = 1 }
83
  }
84

            
85
  # Finished
86
  $self->{state} = $finish ? 'finished' : 'read'
87
    if $self->{write} <= 0 || (defined $buffer && !length $buffer);
88

            
89
  return defined $buffer ? $buffer : '';
90
}
91

            
92
sub _headers {
93
  my ($self, $msg, $head) = @_;
94

            
95
  # Prepare header chunk
96
  my $buffer = $msg->get_header_chunk($self->{offset});
97
  my $written = defined $buffer ? length $buffer : 0;
98
  $self->{write}  = $self->{write} - $written;
99
  $self->{offset} = $self->{offset} + $written;
100

            
101
  # Switch to body
102
  if ($self->{write} <= 0) {
103
    $self->{offset} = 0;
104

            
105
    # Response without body
106
    if ($head && $self->is_empty) { $self->{state} = 'finished' }
107

            
108
    # Body
109
    else {
110
      $self->{http_state} = 'body';
111
      $self->{write} = $msg->content->is_dynamic ? 1 : $msg->body_size;
112
    }
113
  }
114

            
115
  return $buffer;
116
}
117

            
118
sub _start_line {
119
  my ($self, $msg) = @_;
120

            
121
  # Prepare start line chunk
122
  my $buffer = $msg->get_start_line_chunk($self->{offset});
123
  my $written = defined $buffer ? length $buffer : 0;
124
  $self->{write}  = $self->{write} - $written;
125
  $self->{offset} = $self->{offset} + $written;
126

            
127
  # Switch to headers
128
  if ($self->{write} <= 0) {
129
    $self->{http_state} = 'headers';
130
    $self->{write}      = $msg->header_size;
131
    $self->{offset}     = 0;
132
  }
133

            
134
  return $buffer;
135
}
136

            
137
sub _write {
138
  my ($self, $server) = @_;
139

            
140
  # Client starts writing right away
141
  $self->{state} ||= 'write' unless $server;
142
  return '' unless $self->{state} eq 'write';
143

            
144
  # Nothing written yet
145
  $self->{$_} ||= 0 for qw(offset write);
146
  my $msg = $server ? $self->res : $self->req;
147
  unless ($self->{http_state}) {
148

            
149
    # Connection header
150
    my $headers = $msg->headers;
151
    $headers->connection($self->keep_alive ? 'keep-alive' : 'close')
152
      unless $headers->connection;
153

            
154
    # Switch to start line
155
    $self->{http_state} = 'start_line';
156
    $self->{write}      = $msg->start_line_size;
157
  }
158

            
159
  # Start line
160
  my $chunk = '';
161
  $chunk .= $self->_start_line($msg) if $self->{http_state} eq 'start_line';
162

            
163
  # Headers
164
  $chunk .= $self->_headers($msg, $server) if $self->{http_state} eq 'headers';
165

            
166
  # Body
167
  $chunk .= $self->_body($msg, $server) if $self->{http_state} eq 'body';
168

            
169
  return $chunk;
170
}
171

            
172
1;
173

            
174
=encoding utf8
175

            
176
=head1 NAME
177

            
178
Mojo::Transaction::HTTP - HTTP transaction
179

            
180
=head1 SYNOPSIS
181

            
182
  use Mojo::Transaction::HTTP;
183

            
184
  # Client
185
  my $tx = Mojo::Transaction::HTTP->new;
186
  $tx->req->method('GET');
187
  $tx->req->url->parse('http://example.com');
188
  $tx->req->headers->accept('application/json');
189
  say $tx->res->code;
190
  say $tx->res->headers->content_type;
191
  say $tx->res->body;
192
  say $tx->remote_address;
193

            
194
  # Server
195
  my $tx = Mojo::Transaction::HTTP->new;
196
  say $tx->req->method;
197
  say $tx->req->url->to_abs;
198
  say $tx->req->headers->accept;
199
  say $tx->remote_address;
200
  $tx->res->code(200);
201
  $tx->res->headers->content_type('text/plain');
202
  $tx->res->body('Hello World!');
203

            
204
=head1 DESCRIPTION
205

            
206
L<Mojo::Transaction::HTTP> is a container for HTTP transactions as described
207
in RFC 2616.
208

            
209
=head1 EVENTS
210

            
211
L<Mojo::Transaction::HTTP> inherits all events from L<Mojo::Transaction> and
212
can emit the following new ones.
213

            
214
=head2 request
215

            
216
  $tx->on(request => sub {
217
    my $tx = shift;
218
    ...
219
  });
220

            
221
Emitted when a request is ready and needs to be handled.
222

            
223
  $tx->on(request => sub {
224
    my $tx = shift;
225
    $tx->res->headers->header('X-Bender' => 'Bite my shiny metal ass!');
226
  });
227

            
228
=head2 unexpected
229

            
230
  $tx->on(unexpected => sub {
231
    my ($tx, $res) = @_;
232
    ...
233
  });
234

            
235
Emitted for unexpected C<1xx> responses that will be ignored.
236

            
237
  $tx->on(unexpected => sub {
238
    my $tx = shift;
239
    $tx->res->on(finish => sub { say 'Followup response is finished.' });
240
  });
241

            
242
=head2 upgrade
243

            
244
  $tx->on(upgrade => sub {
245
    my ($tx, $ws) = @_;
246
    ...
247
  });
248

            
249
Emitted when transaction gets upgraded to a L<Mojo::Transaction::WebSocket>
250
object.
251

            
252
  $tx->on(upgrade => sub {
253
    my ($tx, $ws) = @_;
254
    $ws->res->headers->header('X-Bender' => 'Bite my shiny metal ass!');
255
  });
256

            
257
=head1 ATTRIBUTES
258

            
259
L<Mojo::Transaction::HTTP> inherits all attributes from L<Mojo::Transaction>
260
and implements the following new ones.
261

            
262
=head2 previous
263

            
264
  my $previous = $tx->previous;
265
  $tx          = $tx->previous(Mojo::Transaction->new);
266

            
267
Previous transaction that triggered this followup transaction.
268

            
269
  # Paths of previous requests
270
  say $tx->previous->previous->req->url->path;
271
  say $tx->previous->req->url->path;
272

            
273
=head1 METHODS
274

            
275
L<Mojo::Transaction::HTTP> inherits all methods from L<Mojo::Transaction> and
276
implements the following new ones.
277

            
278
=head2 client_read
279

            
280
  $tx->client_read($bytes);
281

            
282
Read data client-side, used to implement user agents.
283

            
284
=head2 client_write
285

            
286
  my $bytes = $tx->client_write;
287

            
288
Write data client-side, used to implement user agents.
289

            
290
=head2 is_empty
291

            
292
  my $bool = $tx->is_empty;
293

            
294
Check transaction for C<HEAD> request and C<1xx>, C<204> or C<304> response.
295

            
296
=head2 keep_alive
297

            
298
  my $bool = $tx->keep_alive;
299

            
300
Check if connection can be kept alive.
301

            
302
=head2 redirects
303

            
304
  my $redirects = $tx->redirects;
305

            
306
Return a list of all previous transactions that preceded this followup
307
transaction.
308

            
309
  # Paths of all previous requests
310
  say $_->req->url->path for @{$tx->redirects};
311

            
312
=head2 server_read
313

            
314
  $tx->server_read($bytes);
315

            
316
Read data server-side, used to implement web servers.
317

            
318
=head2 server_write
319

            
320
  my $bytes = $tx->server_write;
321

            
322
Write data server-side, used to implement web servers.
323

            
324
=head1 SEE ALSO
325

            
326
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
327

            
328
=cut