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
|