1# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
2#
3# $Id: httpserver.rb 36958 2012-09-13 02:22:10Z zzak $
4#
5
6
7require "gserver"
8
9# Implements a simple HTTP-server by using John W. Small's (jsmall@laser.net)
10# ruby-generic-server: GServer.
11class HttpServer < GServer
12
13  ##
14  # +handle_obj+ specifies the object, that receives calls from +request_handler+
15  # and +ip_auth_handler+
16  def initialize(handle_obj, port = 8080, host = DEFAULT_HOST, maxConnections = 4,
17                 stdlog = $stdout, audit = true, debug = true)
18    @handler = handle_obj
19    super(port, host, maxConnections, stdlog, audit, debug)
20  end
21
22private
23
24  CRLF        = "\r\n"
25  HTTP_PROTO  = "HTTP/1.0"
26  SERVER_NAME = "HttpServer (Ruby #{RUBY_VERSION})"
27
28  # Default header for the server name
29  DEFAULT_HEADER = {
30    "Server" => SERVER_NAME
31  }
32
33  # Mapping of status codes and error messages
34  StatusCodeMapping = {
35    200 => "OK",
36    400 => "Bad Request",
37    403 => "Forbidden",
38    405 => "Method Not Allowed",
39    411 => "Length Required",
40    500 => "Internal Server Error"
41  }
42
43  class Request
44    attr_reader :data, :header, :method, :path, :proto
45
46    def initialize(data, method=nil, path=nil, proto=nil)
47      @header, @data = Table.new, data
48      @method, @path, @proto = method, path, proto
49    end
50
51    def content_length
52      len = @header['Content-Length']
53      return nil if len.nil?
54      return len.to_i
55    end
56
57  end
58
59  class Response
60    attr_reader   :header
61    attr_accessor :body, :status, :status_message
62
63    def initialize(status=200)
64      @status = status
65      @status_message = nil
66      @header = Table.new
67    end
68  end
69
70  # A case-insensitive Hash class for HTTP header
71  class Table
72    include Enumerable
73
74    def initialize(hash={})
75      @hash = hash
76      update(hash)
77    end
78
79    def [](key)
80      @hash[key.to_s.capitalize]
81    end
82
83    def []=(key, value)
84      @hash[key.to_s.capitalize] = value
85    end
86
87    def update(hash)
88      hash.each {|k,v| self[k] = v}
89      self
90    end
91
92    def each
93      @hash.each {|k,v| yield k.capitalize, v }
94    end
95
96    # Output the Hash table for the HTTP header
97    def writeTo(port)
98      each { |k,v| port << "#{k}: #{v}" << CRLF }
99    end
100  end # class Table
101
102
103  # Generates a Hash with the HTTP headers
104  def http_header(header=nil) # :doc:
105    new_header = Table.new(DEFAULT_HEADER)
106    new_header.update(header) unless header.nil?
107
108    new_header["Connection"] = "close"
109    new_header["Date"]       = http_date(Time.now)
110
111    new_header
112  end
113
114  # Returns a string which represents the time as rfc1123-date of HTTP-date
115  def http_date( aTime ) # :doc:
116    aTime.gmtime.strftime( "%a, %d %b %Y %H:%M:%S GMT" )
117  end
118
119  # Returns a string which includes the status code message as,
120  # http headers, and body for the response.
121  def http_resp(status_code, status_message=nil, header=nil, body=nil) # :doc:
122    status_message ||= StatusCodeMapping[status_code]
123
124    str = ""
125    str << "#{HTTP_PROTO} #{status_code} #{status_message}" << CRLF
126    http_header(header).writeTo(str)
127    str << CRLF
128    str << body unless body.nil?
129    str
130  end
131
132  # Handles the HTTP request and writes the response back to the client, +io+.
133  #
134  # If an Exception is raised while handling the request, the client will receive
135  # a 500 "Internal Server Error" message.
136  def serve(io) # :doc:
137    # perform IP authentification
138    unless @handler.ip_auth_handler(io)
139      io << http_resp(403, "Forbidden")
140      return
141    end
142
143    # parse first line
144    if io.gets =~ /^(\S+)\s+(\S+)\s+(\S+)/
145      request = Request.new(io, $1, $2, $3)
146    else
147      io << http_resp(400, "Bad Request")
148      return
149    end
150
151    # parse HTTP headers
152    while (line=io.gets) !~ /^(\n|\r)/
153      if line =~ /^([\w-]+):\s*(.*)$/
154        request.header[$1] = $2.strip
155      end
156    end
157
158    io.binmode
159    response = Response.new
160
161    # execute request handler
162    @handler.request_handler(request, response)
163
164    # write response back to the client
165    io << http_resp(response.status, response.status_message,
166                    response.header, response.body)
167
168  rescue Exception
169    io << http_resp(500, "Internal Server Error")
170  end
171
172end # class HttpServer
173
174