Module: ServerSide::HTTP::Server

Defined in:
lib/serverside/http/server.rb

Overview

The HTTP server is implemented as a simple state-machine with the following states: state_initial - initialize request variables. state_request_line - wait for and parse the request line. state_request_headers - wait for and parse header lines. state_request_body - wait for and parse the request body. state_response - send a response. state_done - the connection is closed.

The server supports persistent connections (if the request is in HTTP 1.1). In that case, after responding to the request the state is changed back to request_line.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#requestObject (readonly)

attribute readers



41
42
43
# File 'lib/serverside/http/server.rb', line 41

def request
  @request
end

#response_sentObject (readonly)

attribute readers



41
42
43
# File 'lib/serverside/http/server.rb', line 41

def response_sent
  @response_sent
end

Class Method Details

.newObject

Creates a new server module



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/serverside/http/server.rb', line 23

def self.new
  Module.new do
    # include the HTTP state machine and everything else
    include ServerSide::HTTP::Server
    
    # define a start method for starting the server
    def self.start(addr, port)
      EventMachine::run do
        EventMachine::start_server addr, port, self
      end
    end
    
    # invoke the supplied block for application-specific behaviors.
    yield
  end
end

Instance Method Details

#handle_error(e) ⇒ Object

Handle errors raised while processing a request



73
74
75
76
77
78
# File 'lib/serverside/http/server.rb', line 73

def handle_error(e)
  # if an error is raised, we send an error response
  unless @response_sent || @streaming
    send_response(Response.error(e))
  end
end

#post_initObject

post_init creates a new @in buffer and sets the connection state to initial.



45
46
47
48
49
50
51
# File 'lib/serverside/http/server.rb', line 45

def post_init
  # initialize the in buffer
  @in = ''
  
  # set state to initial
  set_state(:state_initial)
end

#receive_data(data) ⇒ Object

receive_data is a callback invoked whenever new data is received on the connection. The incoming data is added to the @in buffer and the state method is invoked.



56
57
58
59
60
61
# File 'lib/serverside/http/server.rb', line 56

def receive_data(data)
  @in << data
  send(@state)
rescue => e
  handle_error(e)
end

#send_response(resp) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/serverside/http/server.rb', line 144

def send_response(resp)
  unless persist = @request.persistent && resp.persistent?
    resp.headers << CONNECTION_CLOSE
  end
  if resp.should_render?
    send_data(resp.to_s)
  end
  if resp.streaming?
    start_stream_loop(resp.stream_period, resp.stream_proc)
  else
    set_state(persist ? :state_initial : :state_done)
  end
end

#set_state(s) ⇒ Object

set_state is called whenever a state transition occurs. It invokes the state method.



65
66
67
68
69
70
# File 'lib/serverside/http/server.rb', line 65

def set_state(s)
  @state = s
  send(s)
rescue => e
  handle_error(e)
end

#start_stream_loop(period, block) ⇒ Object

starts implements a periodical timer. The timer is invoked until the supplied block returns false or nil. When the



165
166
167
168
169
170
171
172
# File 'lib/serverside/http/server.rb', line 165

def start_stream_loop(period, block)
  @streaming = true
  if block.call(self)
    EventMachine::add_timer(period) {start_stream_loop(period, block)}
  else
    set_state(:state_done)
  end
end

#state_doneObject

state_done closes the connection.



159
160
161
# File 'lib/serverside/http/server.rb', line 159

def state_done
  close_connection_after_writing
end

#state_initialObject

state_initial creates a new request instance and immediately transitions to the request_line state.



82
83
84
85
86
# File 'lib/serverside/http/server.rb', line 82

def state_initial
  @request = ServerSide::HTTP::Request.new(self)
  @response_sent = false
  set_state(:state_request_line)
end

#state_request_bodyObject

state_request_body waits for the request body to arrive and then parses the body. Once the body is parsed, the connection transitions to the response state.



127
128
129
130
131
132
# File 'lib/serverside/http/server.rb', line 127

def state_request_body
  if @in.size >= @request.content_length
    @request.parse_body(@in.slice!(0...@request.content_length))
    set_state(:state_response)
  end
end

#state_request_headersObject

state_request_headers parses each header as it arrives. If too many headers are included or a header exceeds the maximum header size, an error is raised.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/serverside/http/server.rb', line 105

def state_request_headers
  while line = @in.get_line
    # Check header size
    if line.size > MAX_HEADER_SIZE
      raise BadRequestError, "Invalid header size"
    # If the line empty then we move to the next state
    elsif line.empty?
      expecting_body = @request.content_length.to_i > 0
      set_state(expecting_body ? :state_request_body : :state_response)
    else
      @request.parse_header(line)
    end
  end
# rescue PrematureBoundaryError
#   @in.insert(0, line)
#   expecting_body = @request.content_length.to_i > 0
#   set_state(expecting_body ? :state_request_body : :state_response)
end

#state_request_lineObject

state_request_line waits for the HTTP request line and parses it once it arrives. If the request line is too big, an error is raised. The request line supplies information including the



91
92
93
94
95
96
97
98
99
100
# File 'lib/serverside/http/server.rb', line 91

def state_request_line
  # check request line size
  if line = @in.get_line
    if line.size > MAX_REQUEST_LINE_SIZE
      raise BadRequestError, "Invalid request size"
    end
    @request.parse_request_line(line)
    set_state(:state_request_headers)
  end
end

#state_responseObject

state_response invokes the handle method. If no response was sent, an error is raised. After the response is sent, the connection is either closed or goes back to the initial state.



137
138
139
140
141
142
# File 'lib/serverside/http/server.rb', line 137

def state_response
  unless resp = handle(@request)
    raise "No handler found for this URI (#{@request.url})"
  end
  send_response(resp)
end