gserver 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +56 -0
- data/README.md +71 -0
- data/Rakefile +2 -0
- data/gserver.gemspec +23 -0
- data/lib/gserver.rb +310 -0
- data/lib/gserver/version.rb +3 -0
- data/sample/xmlrpc.rb +173 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ac2ef003f2e6a480149eacf3fc3af0ad854d1d23
|
4
|
+
data.tar.gz: 89528c80a5bb9ec990abd98454de7bbe8fe11c7b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 03cfeb39d1a7dea2643af0411c898c889f4d204ef7dee078fa6fa51422d0e33f9d60aa0ada165761411d8272b7e05e72144f727d2042f9034bd642dcb3abd087
|
7
|
+
data.tar.gz: c2fe1aae9435dcd7210306b0dc9587870c90e62dfabb390497637d2fd0ef260d3bf418e738d4d01fc644af24e2c5838c030b93a8db7c0dd574d7d4854b965abe
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the
|
3
|
+
2-clause BSDL (see the file BSDL), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) give non-standard binaries non-standard names, with
|
21
|
+
instructions on where to get the original software distribution.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or binary form,
|
26
|
+
provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the binaries and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard binaries non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under these terms.
|
43
|
+
|
44
|
+
For the list of those files and their copying conditions, see the
|
45
|
+
file LEGAL.
|
46
|
+
|
47
|
+
5. The scripts and library files supplied as input to or produced as
|
48
|
+
output from the software do not automatically fall under the
|
49
|
+
copyright of the software, but belong to whomever generated them,
|
50
|
+
and may be sold commercially, and may be aggregated with this
|
51
|
+
software.
|
52
|
+
|
53
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
54
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
56
|
+
PURPOSE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Gserver
|
2
|
+
|
3
|
+
GServer implements a generic server, featuring thread pool management,
|
4
|
+
simple logging, and multi-server management. See HttpServer in
|
5
|
+
<tt>sample/xmlrpc.rb</tt> in the Ruby standard library for an example of
|
6
|
+
GServer in action.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'gserver'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install gserver
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Using GServer is simple. Below we implement a simple time server, run it,
|
27
|
+
query it, and shut it down. Try this code in +irb+:
|
28
|
+
|
29
|
+
require 'gserver'
|
30
|
+
|
31
|
+
#
|
32
|
+
# A server that returns the time in seconds since 1970.
|
33
|
+
#
|
34
|
+
class TimeServer < GServer
|
35
|
+
def initialize(port=10001, *args)
|
36
|
+
super(port, *args)
|
37
|
+
end
|
38
|
+
def serve(io)
|
39
|
+
io.puts(Time.now.to_i)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Run the server with logging enabled (it's a separate thread).
|
44
|
+
server = TimeServer.new
|
45
|
+
server.audit = true # Turn logging on.
|
46
|
+
server.start
|
47
|
+
|
48
|
+
# *** Now point your browser to http://localhost:10001 to see it working ***
|
49
|
+
|
50
|
+
# See if it's still running.
|
51
|
+
GServer.in_service?(10001) # -> true
|
52
|
+
server.stopped? # -> false
|
53
|
+
|
54
|
+
# Shut the server down gracefully.
|
55
|
+
server.shutdown
|
56
|
+
|
57
|
+
# Alternatively, stop it immediately.
|
58
|
+
GServer.stop(10001)
|
59
|
+
# or, of course, "server.stop".
|
60
|
+
|
61
|
+
All the business of accepting connections and exception handling is taken
|
62
|
+
care of. All we have to do is implement the method that actually serves the
|
63
|
+
client.
|
64
|
+
|
65
|
+
## Contributing
|
66
|
+
|
67
|
+
1. Fork it ( https://github.com/[my-github-username]/gserver/fork )
|
68
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
69
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
70
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
71
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/gserver.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gserver/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gserver"
|
8
|
+
spec.version = Gserver::VERSION
|
9
|
+
spec.authors = ["John W. Small", "SHIBATA Hiroshi"]
|
10
|
+
spec.email = ["hsbt@ruby-lang.org"]
|
11
|
+
spec.summary = %q{GServer implements a generic server}
|
12
|
+
spec.description = %q{GServer implements a generic server}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "Ruby"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
data/lib/gserver.rb
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2001 John W. Small All Rights Reserved
|
3
|
+
#
|
4
|
+
# Author:: John W. Small
|
5
|
+
# Documentation:: Gavin Sinclair
|
6
|
+
# Licence:: Ruby License
|
7
|
+
|
8
|
+
require "socket"
|
9
|
+
require "thread"
|
10
|
+
|
11
|
+
#
|
12
|
+
# GServer implements a generic server, featuring thread pool management,
|
13
|
+
# simple logging, and multi-server management. See HttpServer in
|
14
|
+
# <tt>xmlrpc/httpserver.rb</tt> in the Ruby standard library for an example of
|
15
|
+
# GServer in action.
|
16
|
+
#
|
17
|
+
# Any kind of application-level server can be implemented using this class.
|
18
|
+
# It accepts multiple simultaneous connections from clients, up to an optional
|
19
|
+
# maximum number. Several _services_ (i.e. one service per TCP port) can be
|
20
|
+
# run simultaneously, and stopped at any time through the class method
|
21
|
+
# <tt>GServer.stop(port)</tt>. All the threading issues are handled, saving
|
22
|
+
# you the effort. All events are optionally logged, but you can provide your
|
23
|
+
# own event handlers if you wish.
|
24
|
+
#
|
25
|
+
# == Example
|
26
|
+
#
|
27
|
+
# Using GServer is simple. Below we implement a simple time server, run it,
|
28
|
+
# query it, and shut it down. Try this code in +irb+:
|
29
|
+
#
|
30
|
+
# require 'gserver'
|
31
|
+
#
|
32
|
+
# #
|
33
|
+
# # A server that returns the time in seconds since 1970.
|
34
|
+
# #
|
35
|
+
# class TimeServer < GServer
|
36
|
+
# def initialize(port=10001, *args)
|
37
|
+
# super(port, *args)
|
38
|
+
# end
|
39
|
+
# def serve(io)
|
40
|
+
# io.puts(Time.now.to_i)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # Run the server with logging enabled (it's a separate thread).
|
45
|
+
# server = TimeServer.new
|
46
|
+
# server.audit = true # Turn logging on.
|
47
|
+
# server.start
|
48
|
+
#
|
49
|
+
# # *** Now point your browser to http://localhost:10001 to see it working ***
|
50
|
+
#
|
51
|
+
# # See if it's still running.
|
52
|
+
# GServer.in_service?(10001) # -> true
|
53
|
+
# server.stopped? # -> false
|
54
|
+
#
|
55
|
+
# # Shut the server down gracefully.
|
56
|
+
# server.shutdown
|
57
|
+
#
|
58
|
+
# # Alternatively, stop it immediately.
|
59
|
+
# GServer.stop(10001)
|
60
|
+
# # or, of course, "server.stop".
|
61
|
+
#
|
62
|
+
# All the business of accepting connections and exception handling is taken
|
63
|
+
# care of. All we have to do is implement the method that actually serves the
|
64
|
+
# client.
|
65
|
+
#
|
66
|
+
# === Advanced
|
67
|
+
#
|
68
|
+
# As the example above shows, the way to use GServer is to subclass it to
|
69
|
+
# create a specific server, overriding the +serve+ method. You can override
|
70
|
+
# other methods as well if you wish, perhaps to collect statistics, or emit
|
71
|
+
# more detailed logging.
|
72
|
+
#
|
73
|
+
# * #connecting
|
74
|
+
# * #disconnecting
|
75
|
+
# * #starting
|
76
|
+
# * #stopping
|
77
|
+
#
|
78
|
+
# The above methods are only called if auditing is enabled, via #audit=.
|
79
|
+
#
|
80
|
+
# You can also override #log and #error if, for example, you wish to use a
|
81
|
+
# more sophisticated logging system.
|
82
|
+
#
|
83
|
+
class GServer
|
84
|
+
|
85
|
+
DEFAULT_HOST = "127.0.0.1"
|
86
|
+
|
87
|
+
def serve(io)
|
88
|
+
end
|
89
|
+
|
90
|
+
@@services = {} # Hash of opened ports, i.e. services
|
91
|
+
@@servicesMutex = Mutex.new
|
92
|
+
|
93
|
+
# Stop the server running on the given port, bound to the given host
|
94
|
+
#
|
95
|
+
# +port+:: port, as a Fixnum, of the server to stop
|
96
|
+
# +host+:: host on which to find the server to stop
|
97
|
+
def GServer.stop(port, host = DEFAULT_HOST)
|
98
|
+
@@servicesMutex.synchronize {
|
99
|
+
@@services[host][port].stop
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check if a server is running on the given port and host
|
104
|
+
#
|
105
|
+
# +port+:: port, as a Fixnum, of the server to check
|
106
|
+
# +host+:: host on which to find the server to check
|
107
|
+
#
|
108
|
+
# Returns true if a server is running on that port and host.
|
109
|
+
def GServer.in_service?(port, host = DEFAULT_HOST)
|
110
|
+
@@services.has_key?(host) and
|
111
|
+
@@services[host].has_key?(port)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Stop the server
|
115
|
+
def stop
|
116
|
+
@connectionsMutex.synchronize {
|
117
|
+
if @tcpServerThread
|
118
|
+
@tcpServerThread.raise "stop"
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns true if the server has stopped.
|
124
|
+
def stopped?
|
125
|
+
@tcpServerThread == nil
|
126
|
+
end
|
127
|
+
|
128
|
+
# Schedule a shutdown for the server
|
129
|
+
def shutdown
|
130
|
+
@shutdown = true
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return the current number of connected clients
|
134
|
+
def connections
|
135
|
+
@connections.size
|
136
|
+
end
|
137
|
+
|
138
|
+
# Join with the server thread
|
139
|
+
def join
|
140
|
+
@tcpServerThread.join if @tcpServerThread
|
141
|
+
end
|
142
|
+
|
143
|
+
# Port on which to listen, as a Fixnum
|
144
|
+
attr_reader :port
|
145
|
+
# Host on which to bind, as a String
|
146
|
+
attr_reader :host
|
147
|
+
# Maximum number of connections to accept at a time, as a Fixnum
|
148
|
+
attr_reader :maxConnections
|
149
|
+
# IO Device on which log messages should be written
|
150
|
+
attr_accessor :stdlog
|
151
|
+
# Set to true to cause the callbacks #connecting, #disconnecting, #starting,
|
152
|
+
# and #stopping to be called during the server's lifecycle
|
153
|
+
attr_accessor :audit
|
154
|
+
# Set to true to show more detailed logging
|
155
|
+
attr_accessor :debug
|
156
|
+
|
157
|
+
# Called when a client connects, if auditing is enabled.
|
158
|
+
#
|
159
|
+
# +client+:: a TCPSocket instance representing the client that connected
|
160
|
+
#
|
161
|
+
# Return true to allow this client to connect, false to prevent it.
|
162
|
+
def connecting(client)
|
163
|
+
addr = client.peeraddr
|
164
|
+
log("#{self.class} #{@host}:#{@port} client:#{addr[1]} " +
|
165
|
+
"#{addr[2]}<#{addr[3]}> connect")
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Called when a client disconnects, if audition is enabled.
|
171
|
+
#
|
172
|
+
# +clientPort+:: the port of the client that is connecting
|
173
|
+
def disconnecting(clientPort)
|
174
|
+
log("#{self.class} #{@host}:#{@port} " +
|
175
|
+
"client:#{clientPort} disconnect")
|
176
|
+
end
|
177
|
+
|
178
|
+
protected :connecting, :disconnecting
|
179
|
+
|
180
|
+
# Called when the server is starting up, if auditing is enabled.
|
181
|
+
def starting()
|
182
|
+
log("#{self.class} #{@host}:#{@port} start")
|
183
|
+
end
|
184
|
+
|
185
|
+
# Called when the server is shutting down, if auditing is enabled.
|
186
|
+
def stopping()
|
187
|
+
log("#{self.class} #{@host}:#{@port} stop")
|
188
|
+
end
|
189
|
+
|
190
|
+
protected :starting, :stopping
|
191
|
+
|
192
|
+
# Called if #debug is true whenever an unhandled exception is raised.
|
193
|
+
# This implementation simply logs the backtrace.
|
194
|
+
#
|
195
|
+
# +detail+:: the Exception that was caught
|
196
|
+
def error(detail)
|
197
|
+
log(detail.backtrace.join("\n"))
|
198
|
+
end
|
199
|
+
|
200
|
+
# Log a message to #stdlog, if it's defined. This implementation
|
201
|
+
# outputs the timestamp and message to the log.
|
202
|
+
#
|
203
|
+
# +msg+:: the message to log
|
204
|
+
def log(msg)
|
205
|
+
if @stdlog
|
206
|
+
@stdlog.puts("[#{Time.new.ctime}] %s" % msg)
|
207
|
+
@stdlog.flush
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
protected :error, :log
|
212
|
+
|
213
|
+
# Create a new server
|
214
|
+
#
|
215
|
+
# +port+:: the port, as a Fixnum, on which to listen
|
216
|
+
# +host+:: the host to bind to
|
217
|
+
# +maxConnections+:: the maximum number of simultaneous connections to
|
218
|
+
# accept
|
219
|
+
# +stdlog+:: IO device on which to log messages
|
220
|
+
# +audit+:: if true, lifecycle callbacks will be called. See #audit
|
221
|
+
# +debug+:: if true, error messages are logged. See #debug
|
222
|
+
def initialize(port, host = DEFAULT_HOST, maxConnections = 4,
|
223
|
+
stdlog = $stderr, audit = false, debug = false)
|
224
|
+
@tcpServerThread = nil
|
225
|
+
@port = port
|
226
|
+
@host = host
|
227
|
+
@maxConnections = maxConnections
|
228
|
+
@connections = []
|
229
|
+
@connectionsMutex = Mutex.new
|
230
|
+
@connectionsCV = ConditionVariable.new
|
231
|
+
@stdlog = stdlog
|
232
|
+
@audit = audit
|
233
|
+
@debug = debug
|
234
|
+
end
|
235
|
+
|
236
|
+
# Start the server if it isn't already running
|
237
|
+
#
|
238
|
+
# +maxConnections+::
|
239
|
+
# override +maxConnections+ given to the constructor. A negative
|
240
|
+
# value indicates that the value from the constructor should be used.
|
241
|
+
def start(maxConnections = -1)
|
242
|
+
raise "server is already running" if !stopped?
|
243
|
+
@shutdown = false
|
244
|
+
@maxConnections = maxConnections if maxConnections > 0
|
245
|
+
@@servicesMutex.synchronize {
|
246
|
+
if GServer.in_service?(@port,@host)
|
247
|
+
raise "Port already in use: #{host}:#{@port}!"
|
248
|
+
end
|
249
|
+
@tcpServer = TCPServer.new(@host,@port)
|
250
|
+
@port = @tcpServer.addr[1]
|
251
|
+
@@services[@host] = {} unless @@services.has_key?(@host)
|
252
|
+
@@services[@host][@port] = self;
|
253
|
+
}
|
254
|
+
@tcpServerThread = Thread.new {
|
255
|
+
begin
|
256
|
+
starting if @audit
|
257
|
+
while !@shutdown
|
258
|
+
@connectionsMutex.synchronize {
|
259
|
+
while @connections.size >= @maxConnections
|
260
|
+
@connectionsCV.wait(@connectionsMutex)
|
261
|
+
end
|
262
|
+
}
|
263
|
+
client = @tcpServer.accept
|
264
|
+
Thread.new(client) { |myClient|
|
265
|
+
@connections << Thread.current
|
266
|
+
begin
|
267
|
+
myPort = myClient.peeraddr[1]
|
268
|
+
serve(myClient) if !@audit or connecting(myClient)
|
269
|
+
rescue => detail
|
270
|
+
error(detail) if @debug
|
271
|
+
ensure
|
272
|
+
begin
|
273
|
+
myClient.close
|
274
|
+
rescue
|
275
|
+
end
|
276
|
+
@connectionsMutex.synchronize {
|
277
|
+
@connections.delete(Thread.current)
|
278
|
+
@connectionsCV.signal
|
279
|
+
}
|
280
|
+
disconnecting(myPort) if @audit
|
281
|
+
end
|
282
|
+
}
|
283
|
+
end
|
284
|
+
rescue => detail
|
285
|
+
error(detail) if @debug
|
286
|
+
ensure
|
287
|
+
begin
|
288
|
+
@tcpServer.close
|
289
|
+
rescue
|
290
|
+
end
|
291
|
+
if @shutdown
|
292
|
+
@connectionsMutex.synchronize {
|
293
|
+
while @connections.size > 0
|
294
|
+
@connectionsCV.wait(@connectionsMutex)
|
295
|
+
end
|
296
|
+
}
|
297
|
+
else
|
298
|
+
@connections.each { |c| c.raise "stop" }
|
299
|
+
end
|
300
|
+
@tcpServerThread = nil
|
301
|
+
@@servicesMutex.synchronize {
|
302
|
+
@@services[@host].delete(@port)
|
303
|
+
}
|
304
|
+
stopping if @audit
|
305
|
+
end
|
306
|
+
}
|
307
|
+
self
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
data/sample/xmlrpc.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
|
2
|
+
#
|
3
|
+
# $Id$
|
4
|
+
#
|
5
|
+
|
6
|
+
|
7
|
+
require "gserver"
|
8
|
+
|
9
|
+
# Implements a simple HTTP-server by using John W. Small's (jsmall@laser.net)
|
10
|
+
# ruby-generic-server: GServer.
|
11
|
+
class 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
|
+
|
22
|
+
private
|
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 authentication
|
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
|
+
|
172
|
+
end # class HttpServer
|
173
|
+
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gserver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John W. Small
|
8
|
+
- SHIBATA Hiroshi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-08-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.7'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.7'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
description: GServer implements a generic server
|
43
|
+
email:
|
44
|
+
- hsbt@ruby-lang.org
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- gserver.gemspec
|
55
|
+
- lib/gserver.rb
|
56
|
+
- lib/gserver/version.rb
|
57
|
+
- sample/xmlrpc.rb
|
58
|
+
homepage: ''
|
59
|
+
licenses:
|
60
|
+
- Ruby
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.4.1
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: GServer implements a generic server
|
82
|
+
test_files: []
|
83
|
+
has_rdoc:
|