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
-
#request ⇒ Object
readonly
attribute readers.
-
#response_sent ⇒ Object
readonly
attribute readers.
Class Method Summary collapse
-
.new ⇒ Object
Creates a new server module.
Instance Method Summary collapse
-
#handle_error(e) ⇒ Object
Handle errors raised while processing a request.
-
#post_init ⇒ Object
post_init creates a new @in buffer and sets the connection state to initial.
-
#receive_data(data) ⇒ Object
receive_data is a callback invoked whenever new data is received on the connection.
- #send_response(resp) ⇒ Object
-
#set_state(s) ⇒ Object
set_state is called whenever a state transition occurs.
-
#start_stream_loop(period, block) ⇒ Object
starts implements a periodical timer.
-
#state_done ⇒ Object
state_done closes the connection.
-
#state_initial ⇒ Object
state_initial creates a new request instance and immediately transitions to the request_line state.
-
#state_request_body ⇒ Object
state_request_body waits for the request body to arrive and then parses the body.
-
#state_request_headers ⇒ Object
state_request_headers parses each header as it arrives.
-
#state_request_line ⇒ Object
state_request_line waits for the HTTP request line and parses it once it arrives.
-
#state_response ⇒ Object
state_response invokes the handle method.
Instance Attribute Details
#request ⇒ Object (readonly)
attribute readers
41 42 43 |
# File 'lib/serverside/http/server.rb', line 41 def request @request end |
#response_sent ⇒ Object (readonly)
attribute readers
41 42 43 |
# File 'lib/serverside/http/server.rb', line 41 def response_sent @response_sent end |
Class Method Details
.new ⇒ Object
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_init ⇒ Object
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_done ⇒ Object
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_initial ⇒ Object
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_body ⇒ Object
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_headers ⇒ Object
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_line ⇒ Object
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_response ⇒ Object
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 |