summaryrefslogtreecommitdiff
path: root/lib/xmlrpc/httpserver.rb
blob: 66d52139dbf4932a3e5edb2d8f628e96a01c66ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#
# Implements a simple HTTP-server by using John W. Small's (jsmall@laser.net)
# ruby-generic-server.
#
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
#
# $Id$
#


require "gserver"

class HttpServer < GServer

  ##
  # handle_obj specifies the object, that receives calls to request_handler
  # and ip_auth_handler
  def initialize(handle_obj, port = 8080, host = DEFAULT_HOST, maxConnections = 4,
                 stdlog = $stdout, audit = true, debug = true)
    @handler = handle_obj
    super(port, host, maxConnections, stdlog, audit, debug)
  end

private

  # Constants -----------------------------------------------

  CRLF        = "\r\n"
  HTTP_PROTO  = "HTTP/1.0"
  SERVER_NAME = "HttpServer (Ruby #{RUBY_VERSION})"

  DEFAULT_HEADER = {
    "Server" => SERVER_NAME
  }

  ##
  # Mapping of status code and error message
  #
  StatusCodeMapping = {
    200 => "OK",
    400 => "Bad Request",
    403 => "Forbidden",
    405 => "Method Not Allowed",
    411 => "Length Required",
    500 => "Internal Server Error"
  }

  # Classes -------------------------------------------------

  class Request
    attr_reader :data, :header, :method, :path, :proto

    def initialize(data, method=nil, path=nil, proto=nil)
      @header, @data = Table.new, data
      @method, @path, @proto = method, path, proto
    end

    def content_length
      len = @header['Content-Length']
      return nil if len.nil?
      return len.to_i
    end

  end

  class Response
    attr_reader   :header
    attr_accessor :body, :status, :status_message

    def initialize(status=200)
      @status = status
      @status_message = nil
      @header = Table.new
    end
  end


  ##
  # a case-insensitive Hash class for HTTP header
  #
  class Table
    include Enumerable

    def initialize(hash={})
      @hash = hash
      update(hash)
    end

    def [](key)
      @hash[key.to_s.capitalize]
    end

    def []=(key, value)
      @hash[key.to_s.capitalize] = value
    end

    def update(hash)
      hash.each {|k,v| self[k] = v}
      self
    end

    def each
      @hash.each {|k,v| yield k.capitalize, v }
    end

    def writeTo(port)
      each { |k,v| port << "#{k}: #{v}" << CRLF }
    end
  end # class Table


  # Helper Methods ------------------------------------------

  def http_header(header=nil)
    new_header = Table.new(DEFAULT_HEADER)
    new_header.update(header) unless header.nil?

    new_header["Connection"] = "close"
    new_header["Date"]       = http_date(Time.now)

    new_header
  end

  def http_date( aTime )
    aTime.gmtime.strftime( "%a, %d %b %Y %H:%M:%S GMT" )
  end

  def http_resp(status_code, status_message=nil, header=nil, body=nil)
    status_message ||= StatusCodeMapping[status_code]

    str = ""
    str << "#{HTTP_PROTO} #{status_code} #{status_message}" << CRLF
    http_header(header).writeTo(str)
    str << CRLF
    str << body unless body.nil?
    str
  end

  # Main Serve Loop -----------------------------------------

  def serve(io)
    # perform IP authentification
    unless @handler.ip_auth_handler(io)
      io << http_resp(403, "Forbidden")
      return
    end

    # parse first line
    if io.gets =~ /^(\S+)\s+(\S+)\s+(\S+)/
      request = Request.new(io, $1, $2, $3)
    else
      io << http_resp(400, "Bad Request")
      return
    end

    # parse HTTP headers
    while (line=io.gets) !~ /^(\n|\r)/
      if line =~ /^([\w-]+):\s*(.*)$/
        request.header[$1] = $2.strip
      end
    end

    io.binmode
    response = Response.new

    # execute request handler
    @handler.request_handler(request, response)

    # write response back to the client
    io << http_resp(response.status, response.status_message,
                    response.header, response.body)

  rescue Exception => e
    io << http_resp(500, "Internal Server Error")
  end

end # class HttpServer