1#
2# httpservlet.rb -- HTTPServlet Module
3#
4# Author: IPR -- Internet Programming with Ruby -- writers
5# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU Yuuzou
6# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7# reserved.
8#
9# $IPR: abstract.rb,v 1.24 2003/07/11 11:16:46 gotoyuzo Exp $
10
11require 'thread'
12
13require 'webrick/htmlutils'
14require 'webrick/httputils'
15require 'webrick/httpstatus'
16
17module WEBrick
18  module HTTPServlet
19    class HTTPServletError < StandardError; end
20
21    ##
22    # AbstractServlet allows HTTP server modules to be reused across multiple
23    # servers and allows encapsulation of functionality.
24    #
25    # By default a servlet will respond to GET, HEAD (through an alias to GET)
26    # and OPTIONS requests.
27    #
28    # By default a new servlet is initialized for every request.  A servlet
29    # instance can be reused by overriding ::get_instance in the
30    # AbstractServlet subclass.
31    #
32    # == A Simple Servlet
33    #
34    #  class Simple < WEBrick::HTTPServlet::AbstractServlet
35    #    def do_GET request, response
36    #      status, content_type, body = do_stuff_with request
37    #
38    #      response.status = status
39    #      response['Content-Type'] = content_type
40    #      response.body = body
41    #    end
42    #
43    #    def do_stuff_with request
44    #      return 200, 'text/plain', 'you got a page'
45    #    end
46    #  end
47    #
48    # This servlet can be mounted on a server at a given path:
49    #
50    #   server.mount '/simple', Simple
51    #
52    # == Servlet Configuration
53    #
54    # Servlets can be configured via initialize.  The first argument is the
55    # HTTP server the servlet is being initialized for.
56    #
57    #  class Configurable < Simple
58    #    def initialize server, color, size
59    #      super server
60    #      @color = color
61    #      @size = size
62    #    end
63    #
64    #    def do_stuff_with request
65    #      content = "<p " \
66    #                %q{style="color: #{@color}; font-size: #{@size}"} \
67    #                ">Hello, World!"
68    #
69    #      return 200, "text/html", content
70    #    end
71    #  end
72    #
73    # This servlet must be provided two arguments at mount time:
74    #
75    #   server.mount '/configurable', Configurable, 'red', '2em'
76
77    class AbstractServlet
78
79      ##
80      # Factory for servlet instances that will handle a request from +server+
81      # using +options+ from the mount point.  By default a new servlet
82      # instance is created for every call.
83
84      def self.get_instance(server, *options)
85        self.new(server, *options)
86      end
87
88      ##
89      # Initializes a new servlet for +server+ using +options+ which are
90      # stored as-is in +@options+.  +@logger+ is also provided.
91
92      def initialize(server, *options)
93        @server = @config = server
94        @logger = @server[:Logger]
95        @options = options
96      end
97
98      ##
99      # Dispatches to a +do_+ method based on +req+ if such a method is
100      # available.  (+do_GET+ for a GET request).  Raises a MethodNotAllowed
101      # exception if the method is not implemented.
102
103      def service(req, res)
104        method_name = "do_" + req.request_method.gsub(/-/, "_")
105        if respond_to?(method_name)
106          __send__(method_name, req, res)
107        else
108          raise HTTPStatus::MethodNotAllowed,
109                "unsupported method `#{req.request_method}'."
110        end
111      end
112
113      ##
114      # Raises a NotFound exception
115
116      def do_GET(req, res)
117        raise HTTPStatus::NotFound, "not found."
118      end
119
120      ##
121      # Dispatches to do_GET
122
123      def do_HEAD(req, res)
124        do_GET(req, res)
125      end
126
127      ##
128      # Returns the allowed HTTP request methods
129
130      def do_OPTIONS(req, res)
131        m = self.methods.grep(/\Ado_([A-Z]+)\z/) {$1}
132        m.sort!
133        res["allow"] = m.join(",")
134      end
135
136      private
137
138      ##
139      # Redirects to a path ending in /
140
141      def redirect_to_directory_uri(req, res)
142        if req.path[-1] != ?/
143          location = WEBrick::HTTPUtils.escape_path(req.path + "/")
144          if req.query_string && req.query_string.bytesize > 0
145            location << "?" << req.query_string
146          end
147          res.set_redirect(HTTPStatus::MovedPermanently, location)
148        end
149      end
150    end
151
152  end
153end
154