1# -*- Mode: Perl; indent-tabs-mode: nil; -*-
2
3package Servlet::Http::HttpServlet;
4
5use base qw(Servlet::GenericServlet);
6use strict;
7use warnings;
8
9use Servlet::ServletException ();
10use Servlet::Http::HttpServletResponse ();
11
12use constant METHOD_DELETE => 'DELETE';
13use constant METHOD_HEAD => 'HEAD';
14use constant METHOD_GET => 'GET';
15use constant METHOD_OPTIONS => 'OPTIONS';
16use constant METHOD_POST => 'POST';
17use constant METHOD_PUT => 'PUT';
18use constant METHOD_TRACE => 'TRACE';
19
20use constant HEADER_IFMODSINCE => 'If-Modified-Since';
21use constant HEADER_LASTMOD => 'Last-Modified';
22
23sub new {
24    my $self = shift;
25
26    $self = fields::new($self) unless ref $self;
27    $self->SUPER::new(@_);
28
29    return $self;
30}
31
32sub doDelete {
33    my $self = shift;
34    my $request = shift;
35    my $response = shift;
36
37    my $msg = 'HTTP method DELETE is not supported';
38    my $protocol = $request->getProtocol();
39    my $code;
40    if ($protocol =~ /1\.1$/) {
41        $code = Servlet::Http::HttpServletResponse::SC_METHOD_NOT_ALLOWED;
42    } else {
43        $code = Servlet::Http::HttpServletResponse::SC_BAD_REQUEST;
44    }
45
46    $response->sendError($code, $msg);
47
48    return 1;
49}
50
51sub doGet {
52    my $self = shift;
53    my $request = shift;
54    my $response = shift;
55
56    my $method = $request->getMethod();
57    my $msg = "HTTP method $method is not supported";
58    my $protocol = $request->getProtocol();
59    my $code;
60    if ($protocol =~ /1\.1$/) {
61        $code = Servlet::Http::HttpServletResponse::SC_METHOD_NOT_ALLOWED;
62    } else {
63        $code = Servlet::Http::HttpServletResponse::SC_BAD_REQUEST;
64    }
65
66    $response->sendError($code, $msg);
67
68    return 1;
69}
70
71sub doHead {
72    my $self = shift;
73    my $request = shift;
74    my $response = shift;
75
76    # use a response wrapper that eats the output handle but sets the
77    # content length appropriately
78
79    my $noBodyResponse =
80        Servlet::Http::HttpServlet::NoBodyResponse->new($response);
81
82    $self->doGet($request, $noBodyResponse);
83    $noBodyResponse->setContentLength();
84
85    return 1;
86}
87
88sub doPost {
89    my $self = shift;
90    my $request = shift;
91    my $response = shift;
92
93    my $msg = 'HTTP method POST is not supported';
94    my $protocol = $request->getProtocol();
95    my $code;
96    if ($protocol =~ /1\.1$/) {
97        $code = Servlet::Http::HttpServletResponse::SC_METHOD_NOT_ALLOWED;
98    } else {
99        $code = Servlet::Http::HttpServletResponse::SC_BAD_REQUEST;
100    }
101
102    $response->sendError($code, $msg);
103
104    return 1;
105}
106
107sub doOptions {
108    my $self = shift;
109    my $request = shift;
110    my $response = shift;
111
112    my @meth;
113
114    # XXX: shouldn't be using can(), since it traverses the
115    # inheritance tree, and we just want to examine the classes
116    # that are descendents of HttpServlet
117
118    if ($self->can('doDelete')) {
119        push @meth, qw(DELETE);
120    }
121    if ($self->can('doGet')) {
122        push @meth, qw(GET HEAD);
123    }
124    if ($self->can('doOptions')) {
125        push @meth, qw(OPTIONS);
126    }
127    if ($self->can('doPost')) {
128        push @meth, qw(POST);
129    }
130    if ($self->can('doPut')) {
131        push @meth, qw(PUT);
132    }
133    if ($self->can('doTrace')) {
134        push @meth, qw(TRACE);
135    }
136
137    $response->setHeader('Allow', join(', ', @meth));
138
139    return 1;
140}
141
142sub doPut {
143    my $self = shift;
144    my $request = shift;
145    my $response = shift;
146
147    my $msg = 'HTTP method PUT is not supported';
148    my $protocol = $request->getProtocol();
149    my $code;
150    if ($protocol =~ /1\.1$/) {
151        $code = Servlet::Http::HttpServletResponse::SC_METHOD_NOT_ALLOWED;
152    } else {
153        $code = Servlet::Http::HttpServletResponse::SC_BAD_REQUEST;
154    }
155
156    $response->sendError($code, $msg);
157
158    return 1;
159}
160
161sub doTrace {
162    my $self = shift;
163    my $request = shift;
164    my $response = shift;
165
166    my $str = sprintf("TRACE %s %s\r\n",
167                      $request->getRequestURI(),
168                      $request->getProtocol());
169
170    for my $name ($request->getHeaderNames()) {
171        $str .= sprintf ("%s: %s\r\n", $name, $request->getHeader($name));
172    }
173
174    $response->setContentType('message/http');
175    $response->setContentLength(length($str));
176    my $out = $response->getOutputHandle();
177    $out->print($str);
178    $out->close();
179
180    return 1;
181}
182
183sub getLastModified {
184    my $self = shift;
185    my $request = shift;
186
187    return -1;
188}
189
190sub service {
191    my $self = shift;
192    my $request = shift;
193    my $response = shift;
194
195    unless ($request->isa('Servlet::Http::HttpServletRequest') &&
196            $response->isa('Servlet::Http::HttpServletResponse')) {
197        my $msg = 'non-HTTP request or response';
198        Servlet::ServletException->throw($msg);
199    }
200
201    my $method = $request->getMethod();
202
203    if ($method eq METHOD_DELETE) {
204        $self->doDelete($request, $response);
205    } elsif ($method eq METHOD_GET) {
206        my $lastmod = $self->getLastModified($request);
207        if ($lastmod == -1) {
208            $self->doGet($request, $response);
209        } else {
210            my $ifmodsince = $request->getDateHeader(HEADER_IFMODSINCE);
211            if ($ifmodsince < ($lastmod / 1000 * 1000)) {
212                $self->maybeSetLastModified($response, $lastmod);
213                $self->doGet($request, $response);
214            } else {
215                my $code = Servlet::Http::HttpServletResponse::SC_NOT_MODIFIED;
216                $response->setStatus($code);
217            }
218        }
219    } elsif ($method eq METHOD_HEAD) {
220        my $lastmod = $self->getLastModified($request);
221        $self->maybeSetLastModified($response, $lastmod);
222        $self->doHead($request, $response);
223    } elsif ($method eq METHOD_OPTIONS) {
224        $self->doOptions($request, $response);
225    } elsif ($method eq METHOD_POST) {
226        $self->doPost($request, $response);
227    } elsif ($method eq METHOD_PUT) {
228        $self->doPut($request, $response);
229    } elsif ($method eq METHOD_TRACE) {
230        $self->doTrace($request, $response);
231    } else {
232        my $msg = "HTTP method $method is not supported";
233        my $code = Servlet::Http::HttpServletResponse::SC_NOT_IMPLEMENTED;
234        $response->sendError($code, $msg);
235    }
236
237    return 1;
238}
239
240sub maybeSetLastModified {
241    my $self = shift;
242    my $response = shift;
243    my $lastmod = shift;
244
245    # don't set the header if it's already been set
246    return 1 if $response->containsHeader(HEADER_LASTMOD);
247
248    $response->setDateHeader(HEADER_LASTMOD, $lastmod) if $lastmod >= 0;
249
250    return 1;
251}
252
2531;
254
255package Servlet::Http::HttpServlet::NoBodyResponse;
256
257use base qw(Servlet::Http::HttpServletResponseWrapper);
258use fields qw(output writer didSetContentLength);
259use strict;
260use warnings;
261
262# simple response wrapper class that gets content length from output
263# handle class
264
265sub new {
266    my $self = shift;
267
268    $self = fields::new($self) unless ref $self;
269    $self->SUPER::new(@_);
270
271    $self->{output} = Servlet::Http::HttpServlet::NoBodyOutputHandle->new();
272    $self->{writer} = undef;
273    $self->{didSetContentLength} = undef;
274
275    return $self;
276}
277
278sub setContentLength {
279    my $self = shift;
280    my $len = shift;
281
282    if ($len) {
283        $self->{response}->setContentLength($len);
284        $self->{didSetContentLength} = 1;
285    } else {
286        unless ($self->{didSetContentLength}) {
287            my $len = $self->{output}->getContentLength();
288            $self->{response}->setContentLength($len);
289        }
290    }
291
292    return 1;
293}
294
295sub getOutputHandle {
296    my $self = shift;
297
298    return $self->{output};
299}
300
301sub getWriter {
302    my $self = shift;
303
304    unless ($self->{writer}) {
305        # XXX
306        return $self->{output};
307    }
308
309    return $self->{writer};
310}
311
3121;
313
314package Servlet::Http::HttpServlet::NoBodyOutputHandle;
315
316use base qw(IO::Handle);
317use fields qw(contentLength);
318use strict;
319use warnings;
320
321# simple output handle class that eats the output data but calculates
322# content length correctly
323
324sub new {
325    my $self = shift;
326
327    $self = $self->SUPER::new(@_);
328    ${*self}{servlet_http_httpservlet_nobodyoutputhandle_contentlength} = 0;
329
330    return $self;
331}
332
333sub getContentLength {
334    my $self = shift;
335
336    return ${*self}{servlet_http_httpservlet_nobodyoutputhandle_contentlength};
337}
338
339sub print {
340    my $self = shift;
341
342    return $self->write(@_);
343}
344
345sub write {
346    my $self = shift;
347    my $str = shift;
348    my $len = shift || length $str;
349
350    ${*self}{servlet_http_httpservlet_nobodyoutputhandle_contentlength} +=
351        $len;
352
353    return 1;
354}
355
3561;
357__END__
358
359=pod
360
361=head1 NAME
362
363Servlet::Http::HttpServlet - HTTP servlet base class
364
365=head1 SYNOPSIS
366
367  $servlet->doDelete($request, $response);
368
369  $servlet->doGet($request, $response);
370
371  $servlet->doHead($request, $response);
372
373  $servlet->doOptions($request, $response);
374
375  $servlet->doPost($request, $response);
376
377  $servlet->doPut($request, $response);
378
379  $servlet->doTrace($request, $response);
380
381  my $time = $servlet->getLastModified($request);
382
383  $servlet->service($request, $response);
384
385=head1 DESCRIPTION
386
387This class acts as a base class for HTTP servlets. Subclasses must
388override at least one method, usually one of these:
389
390=over
391
392=item C<doGet()>
393
394if the servlet supports HTTP GET requests
395
396=item C<doPost()>
397
398for HTTP POST requests
399
400=item C<doPut()>
401
402for HTTP PUT requests
403
404=item C<doDelete()>
405
406for HTTP DELETE requests
407
408=item C<init()> and C<destroy()>
409
410to manage resources that are held for the life of the servlet
411
412=item C<getServletInfo()>
413
414which the servlet uses to provide information about itself
415
416=back
417
418There's almost no reason to override the C<service()> method, which
419handles standard HTTP requests by dispatching them to the handler
420methods for each HTTP request type (the C<doXXX()> methods listed
421above).
422
423Likewise, there's almost no reason to override the C<doOptions()> and
424C<doTrace()> methods.
425
426Servlets typically run on multithreaded servers, so be aware that a
427servlet must handle concurrent requets and be careful to synchronize
428access to shared resources. Shared resources include in-memory data
429such as instance or class variables and external objects such as
430files, database connections, and network connections. See
431L<perlthrtut> for more information on handling multiple threads in a
432Perl program.
433
434=head1 CONSTRUCTOR
435
436=over
437
438=item new()
439
440Does nothing. All of the servlet initialization is done by the
441C<init()> method.
442
443=back
444
445=head1 METHODS
446
447=over
448
449=item doDelete($request, $response)
450
451Called by the server (via the C<service()> method) to allow a servlet
452to handle a DELETE request. The DELETE operation allows a client to
453remove a document or Web page from the server.
454
455This method does not need to be either safe or idempotent. Operations
456requested through DELETE can have side effects for which users can be
457held accountable. When using this method, it may be useful to save a
458copy of the affected resource in temporary storage.
459
460If the request is incorrectly formatted, the method returns an HTTP
461"Bad Request" message.
462
463B<Parameters:>
464
465=over
466
467=item I<$request>
468
469the B<Servlet::Http::HttpServletRequest> object that contains the
470client request
471
472=item I<$response>
473
474the B<Servlet::Http::HttpServletResponse> object that contains the
475servlet response
476
477=back
478
479B<Throws:>
480
481=over
482
483=item B<Servlet::ServletException>
484
485if the request cannot be handled
486
487=item B<Servlet::Util::IOException>
488
489if an input or output error occurs
490
491=back
492
493=item doGet($request, $response)
494
495Called by the server (via the C<service()> method) to allow a servlet
496to handle a GET request.
497
498Overriding this method to support a GET request also automatically
499supports an HTTP HEAD request. A HEAD request is a GET request that
500returns no body in the response, only the response headers.
501
502When overriding this method, read the request data, write the response
503headers, get the response's writer or output handle object, and
504finally, write the response data. It's best to include content type
505and encoding.
506
507The servlet container must write the headers before committing the
508response, because in HTTP the headers must be sent before the response
509body.
510
511Where possible, set the content length, to allow the servlet container
512to use a persistent connection to return its response to the client,
513improving performance. The content length is automatically set if the
514entire response fits inside the response buffer.
515
516The GET method should be safe, that is, without any side effects for
517which users are held responsible. For example, most form queries have
518no side effects. If a client request is intended to change stored
519data, the request should use some other HTTP method.
520
521The GET method should also be idempotent, meaning that it can be
522safely repeated. Sometimes making a method safe also makes it
523idempotent. For example, repeating queries is both safe and
524idempotent, but buying a product online or modifying data is neither
525safe nor idempotent.
526
527If the request is incorrectly formatted, the method returns an HTTP
528"Bad Request" message.
529
530B<Parameters:>
531
532=over
533
534=item I<$request>
535
536the B<Servlet::Http::HttpServletRequest> object that contains the
537client request
538
539=item I<$response>
540
541the B<Servlet::Http::HttpServletResponse> object that contains the
542servlet response
543
544=back
545
546B<Throws:>
547
548=over
549
550=item B<Servlet::ServletException>
551
552if the request cannot be handled
553
554=item B<Servlet::Util::IOException>
555
556if an input or output error occurs
557
558=back
559
560=item doHead($request, $response)
561
562Called by the server (via the C<service()> method) to allow a servlet
563to handle a HEAD request. The client sends a HEAD request when it
564wants to see only the headers. The HEAD method counts the output bytes
565in the response to set the content length accurately.
566
567If you override this method, you can avoide computing the response
568body and just set the response ehaders directly to improve
569performance. Make sure the method you write is both safe and
570idempotent.
571
572If the request is incorrectly formatted, the method returns an HTTP
573"Bad Request" message.
574
575B<Parameters:>
576
577=over
578
579=item I<$request>
580
581the B<Servlet::Http::HttpServletRequest> object that contains the
582client request
583
584=item I<$response>
585
586the B<Servlet::Http::HttpServletResponse> object that contains the
587servlet response
588
589=back
590
591B<Throws:>
592
593=over
594
595=item B<Servlet::ServletException>
596
597if the request cannot be handled
598
599=item B<Servlet::Util::IOException>
600
601if an input or output error occurs
602
603=back
604
605=item doOptions($request, $response)
606
607Called by the server (via the C<service()> method) to allow a servlet
608to handle a OPTIONS request. The OPTIONS request determines which HTTP
609methods the server supports and returns an appropriate header. For
610example, if a servlet overrides C<doGet()>, this method returns the
611following header:
612
613  Allow: GET, HEAD, TRACE, OPTIONS
614
615There's no need to override this method unless the servlet implements
616new HTTP methods beyond those implemented by HTTP 1.1.
617
618If the request is incorrectly formatted, the method returns an HTTP
619"Bad Request" message.
620
621B<Parameters:>
622
623=over
624
625=item I<$request>
626
627the B<Servlet::Http::HttpServletRequest> object that contains the
628client request
629
630=item I<$response>
631
632the B<Servlet::Http::HttpServletResponse> object that contains the
633servlet response
634
635=back
636
637B<Throws:>
638
639=over
640
641=item B<Servlet::ServletException>
642
643if the request cannot be handled
644
645=item B<Servlet::Util::IOException>
646
647if an input or output error occurs
648
649=back
650
651=item doPost($request, $response)
652
653Called by the server (via the C<service()> method) to allow a servlet
654to handle a POST request. The POST method allows the client to send
655data of unlimited length to the Web server.
656
657When overriding this method, read the request data, write the response
658headers, get the response's writer or output handle object, and
659finally, write the response data. It's best to include content type
660and encoding.
661
662The servlet container must write the headers before committing the
663response, because in HTTP the headers must be sent before the response
664body.
665
666Where possible, set the content length, to allow the servlet container
667to use a persistent connection to return its response to the client,
668improving performance. The content length is automatically set if the
669entire response fits inside the response buffer.
670
671When using HTTP 1.1 chunked encoding (which means that the response
672has a Transfer-Encoding header), do not set the content length.
673
674This method does not need to be either safe or idempotent. Operations
675requested through POST can have side effects for which the user can be
676held accountable, for example, updating stored data or buying items
677online.
678
679If the request is incorrectly formatted, the method returns an HTTP
680"Bad Request" message.
681
682B<Parameters:>
683
684=over
685
686=item I<$request>
687
688the B<Servlet::Http::HttpServletRequest> object that contains the
689client request
690
691=item I<$response>
692
693the B<Servlet::Http::HttpServletResponse> object that contains the
694servlet response
695
696=back
697
698B<Throws:>
699
700=over
701
702=item B<Servlet::ServletException>
703
704if the request cannot be handled
705
706=item B<Servlet::Util::IOException>
707
708if an input or output error occurs
709
710=back
711
712=item doPut($request, $response)
713
714Called by the server (via the C<service()> method) to allow a servlet
715to handle a Put request. The PUT operation allows a client to place a
716file on the server and is similar to sending a file by FTP.
717
718When overriding this method, leave intact any content headers sent
719with the request (including Content-Length, Content-Type,
720Content-Transfer-Encoding, Content-Encoding, Content-Base,
721Content-Language, Content-Location, Content-MD5 and Content-Range). If
722your method cannot handle a content header, it must issue an error
723message (HTTP 501 - Not Implemented) and discard the request. For more
724information on HTTP 1.1, see RFC 2068.
725
726This method does not need to be either safe or idempotent. Operations
727that it performs can have side effects for which the user can be held
728accountable. When using this method, it may be useful to save a copy
729of the affected URL in temporary storage.
730
731If the request is incorrectly formatted, the method returns an HTTP
732"Bad Request" message.
733
734B<Parameters:>
735
736=over
737
738=item I<$request>
739
740the B<Servlet::Http::HttpServletRequest> object that contains the
741client request
742
743=item I<$response>
744
745the B<Servlet::Http::HttpServletResponse> object that contains the
746servlet response
747
748=back
749
750B<Throws:>
751
752=over
753
754=item B<Servlet::ServletException>
755
756if the request cannot be handled
757
758=item B<Servlet::Util::IOException>
759
760if an input or output error occurs
761
762=back
763
764=item getLastModified($request)
765
766Returns the time the requested resource was last modified, in
767milliseconds since midnight January 1, 1970 GMT. IF the time is
768unknown, this method returns a negative number (the default).
769
770Servlets that support HTTP GET requests and can quickly determine
771their last modification time should override this method. This makes
772browser and proxy caches work more effectively, reducing the load on
773server and network resources.
774
775B<Parameters:>
776
777=over
778
779=item I<$request>
780
781the B<Servlet::Http::HttpServletRequest> object that contains the
782client request
783
784=back
785
786=item service($request, $response)
787
788Dispatches client requests to the I<doXXX> methods defined in this
789class. There's no need to override this method.
790
791B<Parameters:>
792
793=over
794
795=item I<$request>
796
797the B<Servlet::Http::HttpServletRequest> object that contains the
798client request
799
800=item I<$response>
801
802the B<Servlet::Http::HttpServletResponse> object that contains the
803servlet response
804
805=back
806
807B<Throws:>
808
809=over
810
811=item B<Servlet::ServletException>
812
813if the request cannot be handled
814
815=item B<Servlet::Util::IOException>
816
817if an input or output error occurs
818
819=back
820
821=back
822
823=head1 SEE ALSO
824
825L<Servlet::GenericServlet>,
826L<Servlet::Http::HttpServletRequest>,
827L<Servlet::Http::HttpServletResponse>
828
829=head1 AUTHOR
830
831Brian Moseley, bcm@maz.org
832
833=cut
834