Class: ICAPrb::Server::ICAPServer

Inherits:
Object
  • Object
show all
Defined in:
lib/icaprb/server.rb

Overview

This class contains the network related stuff like waiting for connections. It is the main class of this project.

Constant Summary collapse

SUPPORTED_ICAP_VERSIONS =

supported ICAP versions

['1.0']

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host = 'localhost', port = 1344, options = nil) ⇒ ICAPServer

Create a new ICAP server

  • host the host on which the socket should be bound to

  • port the port on which the socket should be bound to - this is usually 1344

  • options when you want to use TLS, you can pass a Hash containing the following information

    :secure

    true if TLS should be used

    :certificate

    the path of the certificate

    :key

    the path of the key file



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
# File 'lib/icaprb/server.rb', line 31

def initialize(host = 'localhost', port = 1344, options = nil)
  @host, @port = host,port
  @secure = false
  @certificate = nil
  @key = nil
  if (options.is_a? Hash) && (@secure = options[:secure])
    @key = options[:key]
    @certificate = options[:certificate]
  end

  if (options.is_a? Hash) && options[:logfile]
    @logger = Logger.new(options[:logfile])
  else
    @logger = Logger.new(STDOUT)
  end

  if (options.is_a? Hash) && options[:log_level]
    @logger.level = options[:log_level]
  else
    @logger.level = Logger::WARN
  end

  @services = {}

  @enable_tls_1_1 = options[:enable_tls_1_1] unless options.nil?

  @tls_socket = false
  if (options.is_a? Hash) && options[:tls_socket]
    @tls_socket = options[:tls_socket]
  end
end

Instance Attribute Details

#loggerObject

logger for the server; default level is Logger::WARN and it writes to STDOUT



19
20
21
# File 'lib/icaprb/server.rb', line 19

def logger
  @logger
end

#servicesObject

services registered on the server



21
22
23
# File 'lib/icaprb/server.rb', line 21

def services
  @services
end

Instance Method Details

#handle_request(connection, ip) ⇒ Object

this method handles the connection to the client. It will call the parser and sends the request to the service. The service must return anything and handle the request. The important classes are in response.rb This method includes a lot of error handling. It will respond with an error page if

  • The ICAP version is not supported

  • It cannot read the header

  • The method is not supported by the service

  • The request has an upgrade header, which is not supported

  • the client requested an upgrade to tls, but the server has not been configured to use it

  • the client requested a service, which does not exist



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
# File 'lib/icaprb/server.rb', line 103

def handle_request(connection, ip)
  # handles the request
  begin
    parser = RequestParser.new(connection, ip, self)
    parsed_data = parser.parse
  rescue Exception => e
    #puts $@
    logger.error "[PARSER ERROR] Error while parsing request - Error Message is: #{e}"
    Response.display_error_page(connection,400,
                                {http_version: '1.0',http_status: 400, 'title' => 'Invalid Request',
                                 'content' => 'Your client sent a malformed request - please fix it and try it again.'})
    return
  end

  unless SUPPORTED_ICAP_VERSIONS.include? parsed_data[:icap_data][:request_line][:version]
    Response.display_error_page(connection,505,
                                {http_version: '1.0',
                                 http_status: 500,
                                 'title' => 'Unknown ICAP-version used',
                                 'content' => 'We are sorry but your ICAP version is not known by this server.'})
  end

  # send the data to the service framework
  path = parsed_data[:icap_data][:request_line][:uri].path
  path = path[1...path.length] if path != '*'
  if (service = @services[path])
    icap_method = parsed_data[:icap_data][:request_line][:icap_method]
    if icap_method == :options
      return service.generate_options_response(connection)
    else
      if service.supported_methods.include? icap_method
        service.do_process(self,ip,connection,parsed_data)
        return
      else
        Response.display_error_page(connection,405,
                                    {http_version: '1.0',http_status: 500, 'title' => 'ICAP Error',
                                     'content' => 'Your client accessed the service with the wrong method.'})
      end
    end

  elsif (path == '*') &&  (parsed_data[:icap_data][:request_line][:icap_method] == :options)
    # check for an upgrade header
    icap_data = parsed_data[:icap_data]
    if icap_data[:header]['Connection'] == 'Upgrade' && connection.class == OpenSSL::SSL::SSLSocket
      case icap_data[:header]['Upgrade']
        when /^TLS\/[\d\.]+, ICAP\/[\d\.]+$/
          response = Response.new
          response.icap_status_code = 101
          response.icap_header['Upgrade'] = "TLS/1.2, ICAP/#{icap_data[:request_line][:version]}"
          response.write_headers_to_socket connection
          connection.accept # upgrade connection to use tls
        else
          Response.display_error_page(connection,400,{'title' => 'ICAP Error',
                                                      'content' => 'Upgrade header is missing',
                                                      :http_version => '1.1',
                                                      :http_status => 500})
      end
    else
      Response.display_error_page(connection,500,{'title' => 'ICAP Error',
                                                  'content' => 'This server has no TLS support.',
                                                  :http_version => '1.1',
                                                  :http_status => 500})
    end
    return
  else
    Response.display_error_page(connection,404,
                                {http_version: '1.0',http_status: 500, 'title' => 'Not Found',
                                 'content' => 'Sorry, but the ICAP service does not exist.'})
    return
  end

end

#runObject

this methods starts the server and passes the connection to the method handle_request as well as the ip and the port. It will log the information about the connection if the level is set to info or lower.

this method will most likely never crash. It is blocking so you may want to run it in its own thread.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/icaprb/server.rb', line 69

def run
  # run the server
  server = create_server
  loop do

    Thread.start(server.accept) do |connection|

      if connection.is_a? OpenSSL::SSL::SSLSocket
        port, ip = Socket.unpack_sockaddr_in(connection.io.getpeername)
      else
        port, ip = Socket.unpack_sockaddr_in(connection.getpeername)
      end
      @logger.info "[CONNECT] Client from #{ip}:#{port} connected to this server"
      begin
        until connection.closed? do
          handle_request(connection,ip)
        end
      rescue Errno::ECONNRESET => e
        @logger.error "[CONNECTION ERROR] Client #{ip}:#{port} got disconnected (CONNECTION RESET BY PEER): #{e}"
      end
    end

  end
end