Class: ICAPrb::Server::Services::ServiceBase

Inherits:
Object
  • Object
show all
Includes:
Parser::ChunkedEncodingHelper
Defined in:
lib/icaprb/server/services.rb

Overview

Base class for ICAP services

Direct Known Subclasses

EchoService

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(service_name, supported_methods = [], preview_size = nil, options_ttl = 60, transfer_preview = nil, transfer_ignore = nil, transfer_complete = nil, max_connections = 100000) ⇒ ServiceBase

initialize a new service



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/icaprb/server/services.rb', line 46

def initialize(service_name,supported_methods  = [], preview_size = nil, options_ttl = 60,
               transfer_preview = nil, transfer_ignore = nil, transfer_complete = nil, max_connections = 100000)  #TODO Work in progress; sort
  @service_name = service_name
  @options_ttl = options_ttl
  @supported_methods = supported_methods
  @preview_size = preview_size

  @transfer_preview = transfer_preview
  @transfer_ignore = transfer_ignore
  @transfer_complete = transfer_complete
  @max_connections = max_connections
  @is_tag = nil
  @service_id = nil
  @timeout = nil

  @counter = {}
end

Instance Attribute Details

#counterObject (readonly)

the counter is used to determine if too many connections are opened by the proxy. If this is the case, the the server answers with an error



41
42
43
# File 'lib/icaprb/server/services.rb', line 41

def counter
  @counter
end

#is_tagObject

The IS-Tag is a required header. If you change it, the cache of the proxy will be flushed. You usually do not need to change this header. You may want to add your service name here.



35
36
37
# File 'lib/icaprb/server/services.rb', line 35

def is_tag
  @is_tag
end

#max_connectionsObject

Maximum amount of concurrent connections per IP. The server will not accept more connections and answers with an error



32
33
34
# File 'lib/icaprb/server/services.rb', line 32

def max_connections
  @max_connections
end

#options_ttlObject

send the ttl via options header - the options are valid for the given time



11
12
13
# File 'lib/icaprb/server/services.rb', line 11

def options_ttl
  @options_ttl
end

#preview_sizeObject

The preview size has to be set if the server supports previews. Otherwise use nil here. If your service supports previews



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

def preview_size
  @preview_size
end

#service_idObject

If you want to send a service id header to the ICAP client, you can set it here. Use nil to disable this header.



38
39
40
# File 'lib/icaprb/server/services.rb', line 38

def service_id
  @service_id
end

#service_nameObject

the name of the service which is used in the response header (Service-Name)



9
10
11
# File 'lib/icaprb/server/services.rb', line 9

def service_name
  @service_name
end

#supported_methodsObject

the supported methods for this service. This must be an Array which contains symbols. The values are:

:request_mod

request mod is supported

:response_mod

response mod is supported

Do not add :options - this would be wrong here!



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

def supported_methods
  @supported_methods
end

#timeoutObject

timeout for the service



43
44
45
# File 'lib/icaprb/server/services.rb', line 43

def timeout
  @timeout
end

#transfer_completeObject

Array of file extensions which should be always sent to the ICAP server (no preview)



24
25
26
# File 'lib/icaprb/server/services.rb', line 24

def transfer_complete
  @transfer_complete
end

#transfer_ignoreObject

Array of file extensions which should not be sent to the ICAP server (not even a preview)



26
27
28
# File 'lib/icaprb/server/services.rb', line 26

def transfer_ignore
  @transfer_ignore
end

#transfer_previewObject

Array of file extensions which need a preview sent to the ICAP server (do not send the full file in advance)



29
30
31
# File 'lib/icaprb/server/services.rb', line 29

def transfer_preview
  @transfer_preview
end

Instance Method Details

#do_process(server, ip, io, data) ⇒ Object

this method is called by the server when it receives a new ICAP request it will increase the counter by one, call process_request and decreases the counter by one.



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

def do_process(server,ip,io,data)
  begin
    enter(ip)
  rescue
    Response.display_error_page(io,503,{'title' => 'ICAP Error',
                                                'content' => 'Sorry, too much work for me',
                                                :http_version => '1.1',
                                                :http_status => 500})
    return
  end

  begin
    unless @supported_methods.include? data[:icap_data][:request_line][:icap_method]
      Response.display_error_page(io,501,{'title' => 'Method not implemented',
                                                  'content' => 'I do not know what to do with that...',
                                                  :http_version => '1.1',
                                                  :http_status => 500})
      return
    end
    if @timeout
      begin
        Timeout::timeout(@timeout) do
          process_request(server,ip,io,data)
        end
      rescue Timeout::Error => e
        # do not do a graceful shutdown of the connection as the client may fail
        server.logger.error e
        io.close
      end
    else
      process_request(server,ip,io,data)
    end
  rescue
    leave(ip)
    raise
  end
  leave(ip)
end

#enter(ip) ⇒ Object

when the connection enters this method will increase the counter. If the counter exceeds the limit, the request will be rejected



154
155
156
157
158
159
160
161
# File 'lib/icaprb/server/services.rb', line 154

def enter(ip)
  if @counter[ip]
    raise :connection_limit_exceeded unless (@counter[ip] < @max_connections) || @max_connections.nil?
    @counter[ip] += 1
  else
    @counter[ip] = 1
  end
end

#generate_options_response(io) ⇒ Object

This method is called by the server when the client sends an options request which is not a mandatory upgrade.

The data used here is set by the constructor and it should be configured when the Service is initialized.

Parameters: io the socket used to answer the request



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/icaprb/server/services.rb', line 176

def generate_options_response(io)
  response = ::ICAPrb::Server::Response.new
  response.components << ::ICAPrb::Server::NullBody.new
  methods = []
  methods << 'REQMOD' if @supported_methods.include? :request_mod
  methods << 'RESPMOD' if @supported_methods.include? :response_mod
  response.icap_header['Methods'] = methods.join(', ')
  set_generic_icap_headers(response.icap_header)
  response.icap_header['Max-Connections'] = @max_connections if @max_connections
  response.icap_header['Options-TTL'] = @options_ttl if @options_ttl
  response.icap_header['Preview'] = @preview_size if @preview_size
  response.icap_header['Transfer-Ignore'] = @transfer_ignore.join(', ') if @transfer_ignore
  response.icap_header['Transfer-Complete'] = @transfer_complete.join(', ') if @transfer_complete
  response.icap_header['Transfer-Preview'] = @transfer_preview.join(', ') if @transfer_preview
  response.icap_header['Allow'] = '204'
  response.write_headers_to_socket io
end

#get_the_rest_of_the_data(io) ⇒ Object

When we get a preview, we can answer it or request the rest of the data. This method will send the status “100 Continue” to request the rest of the data and it will then request all the data which is left and returns this data as a single string.

You may want to concatenate it with the data you already got in the preview using the << operator.

WARNING: DO NOT CALL THIS METHOD IF YOU ARE NOT IN A PREVIEW!



102
103
104
105
106
107
108
109
# File 'lib/icaprb/server/services.rb', line 102

def get_the_rest_of_the_data(io)
  data = ''
  Response.continue(io)
  until (line,_ = read_chunk(io); line) && line == :eof
    data += line
  end
  return data
end

#got_all_data?(data) ⇒ Boolean

returns true if we already got all data or if we are in a preview. if we are not in a preview, the preview header is not present => outside of a preview and if the ieof is set, there is no data left - we have all data everything else means there is data left to request. NOTE: this will only work once! Do not request data after calling this method and call it again - you will get a false negative.

Returns:

  • (Boolean)


89
90
91
92
93
# File 'lib/icaprb/server/services.rb', line 89

def got_all_data?(data)
  return true unless data[:icap_data][:header]['Preview']
  return true if data[:http_response_body].ieof
  return false
end

#leave(ip) ⇒ Object

when the request is answered we can allow the next one by decrementing the counter



164
165
166
# File 'lib/icaprb/server/services.rb', line 164

def leave(ip)
  @counter[ip] -= 1
end

#process_request(_, _, _, _) ⇒ Object

parameters:

server

reference to the icap server

ip

ip address of the peer

socket

socket to communicate

data

the parsed request



69
70
71
# File 'lib/icaprb/server/services.rb', line 69

def process_request(_,_,_,_)
  raise :not_implemented
end

#set_generic_icap_headers(icap_header) ⇒ Object

set headers independently from the response type

parameters:

icap_header

The hash which holds the ICAP headers.



198
199
200
201
202
# File 'lib/icaprb/server/services.rb', line 198

def set_generic_icap_headers(icap_header)
  icap_header['Service-Name'] = @service_name
  icap_header['ISTag'] = @is_tag if @is_tag
  icap_header['Service-ID'] = @service_id if @service_id
end

#supports_preview?Boolean

returns if this service supports previews which means it can request the rest of the data if they are required. If you do not override this method, this will return false so you will get the complete request.

Returns:

  • (Boolean)


75
76
77
78
# File 'lib/icaprb/server/services.rb', line 75

def supports_preview?
  return false if @preview_size.nil?
  return  preview_size >= 0
end