Class: Diode::Server

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(port, routing = [], env = {}) ⇒ Server

Returns a new instance of Server.



16
17
18
19
20
21
# File 'lib/diode/server.rb', line 16

def initialize(port, routing=[], env={})
  @port = port.to_i()
  @routing = routing
  @env = env
  @filters = [self] # list of filters that requests pass through before dispatch to a servlet
end

Instance Attribute Details

#envObject

Returns the value of attribute env.



14
15
16
# File 'lib/diode/server.rb', line 14

def env
  @env
end

#filtersObject

Returns the value of attribute filters.



14
15
16
# File 'lib/diode/server.rb', line 14

def filters
  @filters
end

Instance Method Details

#complete(conn, response) ⇒ Object

send the response, make sure its all sent



95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/diode/server.rb', line 95

def complete(conn, response)  # send the response, make sure its all sent
  begin
    http = response.to_s
    total = http.bytes.size()
    sent = 0
    while sent < total
      msgsize = conn.send(http.byteslice(sent..-1), 0)
      sent = sent + msgsize
    end
  rescue Errno::EPIPE # ignore, we're finished anyway
  end
end

#read_request(client) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/diode/server.rb', line 64

def read_request(client)
  message = client.recv(2048000)
  unless message.empty?
    unless message.index("Content-Length: ").nil?  # handle large messages such as file uploads
      start = message.index("Content-Length: ")
      stop = message.index("\r\n", start)
      len = message[start..stop].chomp.sub("Content-Length: ","").to_i
      bodyStart = message.index("\r\n\r\n") + 3 # up to end of 4 bytes
      byteStart = message[0..bodyStart].bytes.size
      remaining = len - (message.bytes.size - byteStart)  # we have already read some of the content
      while remaining > 0
        chunk = client.recv(2048000)
        message = message + chunk
        remaining = remaining - chunk.bytes.size
      end
    end
  end
  message
end

#serve(request) ⇒ Object

handle a new request connection



85
86
87
88
89
90
91
92
93
# File 'lib/diode/server.rb', line 85

def serve(request) # keep signature consistent with filters and servlets
  pattern, klass, args = @routing.find{ |pattern, klass, args|
    not pattern.match(request.path).nil?
  }
  raise(Diode::RequestError.new(404)) if klass.nil?
  servlet = klass.new(*args)
  request.pattern = pattern  # provide the mount pattern to the request, useful for Diode::Static
  response = servlet.serve(request)
end

#startObject



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/diode/server.rb', line 23

def start()
  validate_routing()
  Fiber.schedule {
    server = TCPServer.new("127.0.0.1", @port)
    Signal.trap("INT") { exit(0) }
    loop do
      client = server.accept()
      Fiber.schedule(client) { |task, client|
        rawRequest = read_request(client)
        begin
          request = Diode::Request.new(rawRequest)
          request.remote = client.remote_address.ip_address # decorate with request source address
          request.env = @env.dup() # copy environment into request
          request.filters = @filters.dup
          response = (request.filters.shift).serve(request)
        rescue Diode::SecurityError, Diode::RequestError => e
          response = Diode::Response.standard(e.code)
        end
        complete(client, response)
        client.close_write()
      }
    end
  }
end

#url_encode(s) ⇒ Object



108
109
110
111
112
# File 'lib/diode/server.rb', line 108

def url_encode(s)
  s.b.gsub(/([^ a-zA-Z0-9_.-]+)/) { |m|
    '%' + m.unpack('H2' * m.bytesize).join('%').upcase
  }.tr(' ', '+').force_encoding(Encoding::UTF_8) # url-encoded message
end

#validate_routingObject

check routing table is sane



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

def validate_routing()
  newRouting = []
  raise("routing must be an Array") unless @routing.is_a?(Array)
  @routing.each { |pattern, klass, *args|
    raise("invalid pattern='#{pattern}' in routing table") unless pattern.is_a?(Regexp)
    begin
      servletKlass = klass.split("::").inject(Object) { |o,c| o.const_get(c) }
      newRouting << [pattern, servletKlass, args]
    rescue NameError
      raise("unrecognised class #{klass} found in routing table")
    end
  }
  @routing = newRouting # optimised so we can instantiate the servlet quickly
end