青山です。

simple_chat_server.rb の http 部分を分離したついでに、さらにライブラリ
として取り出してみました。汎用として使うにはヘッダの生成の方があまりに
も貧弱(content-length しか無い)ですが、ヘッダの解釈の方はこの程度でも
使えそうです。

ついでに、これを使用したタイプの simple_chat_server.rb も付けます。(圧
縮しようかと思ったのですが、たいして変らないのでそのまま付けます。) 

ところで、CR, LF (できれば EOL も) という定数を用意するというのはいか
がでしょうか? 便利だと思いますし、弊害も無いと思いますが。


#!/usr/local/bin/ruby
#
# http-lib.rb
# ver0.01 1998/04/17
# Wakou Aoyama <wakou / fsinet.or.jp>
#
# == HTTP header analyze
# request_file_name = HTTP.header_analyze(IO)
#
# == add HTTP header
# http_content = HTTP.add_header("string")

class HTTP
  CR  = "\015"
  LF  = "\012"
  EOL = CR + LF

  def HTTP.header_analyze(io) # ===== header analyze
    head = ''
    head += io.read(1) while /#{EOL}#{EOL}|#{LF}#{LF}|#{CR}#{CR}/n !~ head
    head.gsub!(/#{EOL}|#{CR}|#{LF}/, "\n")
    case head
    when /^GET\s+([^?\s]+)\??([^\s]*)/
      request_file_name = $1
      ENV['REQUEST_METHOD'] = 'GET'
      ENV['QUERY_STRING'] = ($2 or '').to_s
    when /^POST\s+([^\s]+)/
      request_file_name = $1
      ENV['REQUEST_METHOD'] = 'POST'
      ENV['CONTENT_LENGTH'] = head.scan(/^Content-length: (\d+)/i)[0].to_s
    end
    head.scan(/\n([^:]+): (.*)/){|k,v|
      ENV['HTTP_' + k.upcase.gsub(/\-/, '_')] = v
    }
    request_file_name
  end

  def HTTP.add_header(content, options = '') # ===== add header
   'HTTP/1.0 200 OK' + EOL +
  ('Content-length: ' + content.length.to_s + EOL if
     'no-length' != options).to_s +
   'Content-type: text/html' + EOL + EOL +
    content
  end
end


#!/usr/local/bin/ruby
#
# simple_chat_server.rb
# ver0.20 1998/04/17
# Wakou Aoyama <wakou / fsinet.or.jp>

require 'socket'
require 'thread'
require 'kconv'
require 'cgi-lib'
require 'http-lib'

CR  = "\015"
LF  = "\012"
EOL = CR + LF

def send_for_all_member(member, message) # ===== send message
  member.each_value do |listener| # delivery for each listener
    begin
      listener['listen_window'] << message # delivery the message
    rescue # if listener was logouted
      handle = listener['handle']
      member.delete(listener['no'])
      send_for_all_member(member, '(' + handle + " is logout.)<BR>\n" )
    end
  end
end

def new_access(sock, serial_no, member) # ===== new access
  member[serial_no] = {'no'     => serial_no,
                       'handle' => 'No. ' + serial_no.to_s}
  sock << HTTP.add_header(
    %|<TITLE>CHAT</TITLE>
      <FRAMESET ROWS="*, 60">
        <FRAME SRC="/listen#{serial_no}.html">
        <FRAME SRC="/talk#{serial_no}.html">
      </FRAMESET>|.gsub(/^      /, ''), 'no-cache')
  sock.shutdown(2)
end

def new_listen_window(sock, my, member) # ===== new listen window
  my['listen_window'] = sock
  sock << HTTP.add_header(
    %|<BODY BGCOLOR="white">
      <H1>Welcome!<BR>simple chat server.</H1>
      <P><B>Powerd by
      <A HREF="http://www.netlab.co.jp/ruby/jp/">
      Ruby</A>.</B></P>
      <P>change handle. input like this.<BR>
      <TT>/HA handle</TT></P>|.gsub(/^      /, ''), 'no-length')
  send_for_all_member(member, '(' + my['handle'] + " is login.)<BR>\n")
  # socket don't shutdown
end

def talk_window(sock, my, member) # ===== talk window (post message)
  query = CGI.new(sock)
  if /\/HA (.+)/i =~ query.inputs['input'] # change handle
    send_for_all_member(member,
      '(change handle. "' + my['handle'] + '" --&gt; "' +
        Kconv.toeuc($1) + "\")<BR>\n" )
    my['handle'] = Kconv.toeuc($1)
  elsif query.inputs.include?('input') and '' != query.inputs['input']
    send_for_all_member(member, my['handle'] + ' &gt; ' +
      Kconv.toeuc( (query.inputs['input'] or '') ) + "<BR>\n" )
  end
  sock << HTTP.add_header(
    %|<BODY BGCOLOR="white">
      <FORM METHOD="post" ACTION="/talk#{my['no']}.html">
        <B>#{my['handle']} &gt; </B>
        <INPUT TYPE="text" NAME="input" SIZE="60">
        <INPUT TYPE="submit" VALUE="send">
      </FORM>|.gsub(/^      /, ''))
  sock.shutdown(2)
end

PORT = ARGV.shift || 8080
serial_no = 0
member = {}

port = TCPserver.open(PORT)
trap('PIPE', 'IGNORE')
STDERR.print (Time.now, "\tserver is on #{port.addr[1]}\n")

loop do
  s = port.accept # stand by for accept

  Thread.start do # divide the work
    sock = s # copy to thread-local variable
    case HTTP.header_analyze(sock)
    when %r|^/$|                 # ===== new access
      serial_no += 1
      new_access(sock, serial_no, member)
    when %r|^/talk(\d+).html$|   # ===== talk window (post message)
      talk_window(sock, member[$1.to_i], member)
    when %r|^/listen(\d+).html$| # ===== new listen window
      new_listen_window(sock, member[$1.to_i], member)
    else                         # ===== file not found
      sock << HTTP.add_header('<H1>file not found</H1>')
      sock.shutdown(2)
    end
  end
end


青山 和光 Wakou Aoyama <wakou / fsinet.or.jp>