* server.rb: Connect $stdout and $stderr to StringIO objects.
[riece] / lisp / server.rb
1 # A simple IPC server executing Ruby programs.
2
3 require 'thread'
4 require 'stringio'
5
6 class Server
7   module B
8     def output(s)
9       @out.puts("# output #{Thread.current[:rubyserv_name]} #{s}\r\n")
10     end
11     module_function :output
12   end
13
14   def initialize
15     @out = $stdout
16     @err = $stderr
17     $stdout = StringIO.new
18     $stderr = StringIO.new
19     out, err = @out, @err
20     B.module_eval do
21       @out, @err = out, err
22     end
23
24     @buf = ''
25     @que = Queue.new
26     @thr = Hash.new
27     @cnt = 0
28   end
29
30   def dispatch(line)
31     case line.chomp
32     when /\AD /
33       @buf << $'
34     when /\A(\S+)\s*/
35       c = $1
36       r = $'
37       d = "dispatch_#{c.downcase}"
38       if respond_to?(d, true)
39         Thread.start do
40           self.send(d, c, r)
41         end
42       else
43         @out.puts("ERR 103 Unknown command\r\n")
44       end
45     end
46   end
47
48   def dispatch_cancel(c, r)
49     @out.puts("ERR 100 Not implemented\r\n")
50   end
51
52   def dispatch_bye(c, r)
53     @out.puts("ERR 100 Not implemented\r\n")
54   end
55
56   def dispatch_auth(c, r)
57     @out.puts("ERR 100 Not implemented\r\n")
58   end
59
60   def dispatch_reset(c, r)
61     @out.puts("ERR 100 Not implemented\r\n")
62   end
63
64   def dispatch_end(c, r)
65     enq_data
66   end
67
68   def dispatch_help(c, r)
69     @out.puts("ERR 100 Not implemented\r\n")
70   end
71
72   def dispatch_quit(c, r)
73     @out.puts("ERR 100 Not implemented\r\n")
74   end
75
76   def dispatch_eval(c, r)
77     r = deq_data if r.empty?
78     name = nil
79     Thread.exclusive do
80       while @thr.include?(name = @cnt.to_s)
81         @cnt += 1
82       end
83       @thr[name] = Thread.current
84     end
85     @out.puts("S name #{name}\r\n")
86     @out.puts("OK\r\n")
87     Thread.current[:rubyserv_name] = name
88     begin
89       Thread.current[:rubyserv_error] = false
90       Thread.current[:rubyserv_response] = eval(r, B.module_eval('binding()'))
91     rescue Exception => e
92       Thread.current[:rubyserv_error] = true
93       Thread.current[:rubyserv_response] = e.to_s.sub(/\A.*?\n/, '')
94     end
95     @out.puts("# exit #{name}\r\n")
96   end
97
98   def dispatch_poll(c, r)
99     thr = @thr[r]
100     if !thr
101       @out.puts("ERR 105 Parameter error: no such name \"#{r}\"\r\n")
102     elsif thr.alive?
103       @out.puts("S running #{r}\r\n")
104       @out.puts("OK\r\n")
105     else
106       if thr[:rubyserv_error]
107         @out.puts("S exited #{r}\r\n")
108       else
109         @out.puts("S finished #{r}\r\n")
110       end
111       if d = thr[:rubyserv_response]
112         send_data(d.to_s)
113       end
114       @out.puts("OK\r\n")
115     end
116   end
117
118   def dispatch_exit(c, r)
119     thr = @thr[r]
120     if !thr
121       @out.puts("ERR 105 Parameter error: no such name \"#{r}\"\r\n")
122       return
123     end
124     thr.kill if thr.alive?
125     @thr.delete(r)
126     @out.puts("OK\r\n")
127   end
128
129   def escape(s)
130     s.gsub(/[%\r\n]/) {|m| '%%%02X' % m[0]}
131   end
132
133   def unescape(s)
134     s.gsub(/%([0-9A-Z][0-9A-Z])/) {[$1].pack('H*')}
135   end
136
137   def send_data(d)
138     d = escape(d)
139     begin
140       len = [d.length, 998].min   # 998 = 1000 - "D "
141       @out.puts("D #{d[0 ... len]}\r\n")
142       d = d[len .. -1]
143     end until d.empty?
144   end
145
146   def enq_data
147     d = unescape(@buf)
148     @buf = ''
149     @que.enq(d)
150   end
151
152   def deq_data
153     @que.deq
154   end
155 end
156
157 if $0 == __FILE__
158   server = Server.new
159   while gets
160     server.dispatch($_)
161   end
162 end