Created
July 31, 2012 09:52
-
-
Save funny-falcon/3215614 to your computer and use it in GitHub Desktop.
Writing a minimalistic web-server using event machine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original article: http://www.franzens.org/2011/10/writing-minimalistic-web-server-using.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubygems' | |
require 'eventmachine' | |
require_relative './thin_http_parser.rb' | |
# Freeze some HTTP header names & values | |
KEEPALIVE = "Connection: Keep-Alive\r\n".freeze | |
class RequestHandler < EM::Connection | |
def post_init | |
@parser = RequestParser.new | |
end | |
def receive_data(data) | |
handle_http_request if @parser.parse(data) | |
end | |
def handle_http_request | |
p [@parser.env, @parser.body.string] | |
keep_alive = @parser.persistent? | |
data = "OK" | |
send_data("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: #{data.bytesize}\r\n#{ keep_alive ? KEEPALIVE.clone : nil}\r\n#{data}") | |
if keep_alive | |
post_init | |
else | |
close_connection_after_writing | |
end | |
end | |
end | |
host,port = "0.0.0.0", 8083 | |
puts "Starting server on #{host}:#{port}, #{EM::set_descriptor_table_size(32768)} sockets" | |
EM.run do | |
EM.start_server host, port, RequestHandler | |
if ARGV.size > 0 | |
forks = ARGV[0].to_i | |
puts "... forking #{forks} times => #{2**forks} instances" | |
forks.times { fork } | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubygems' | |
# Uncomment this line if 'thin_parser' is not found | |
# require 'thin' | |
require 'thin_parser' | |
require 'stringio' | |
# Freeze some HTTP header names & values | |
HTTP_VERSION = 'HTTP_VERSION'.freeze | |
HTTP_1_0 = 'HTTP/1.0'.freeze | |
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze | |
CONNECTION = 'HTTP_CONNECTION'.freeze | |
KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze | |
CLOSE_REGEXP = /\bclose\b/i.freeze | |
# Freeze some Rack header names | |
RACK_INPUT = 'rack.input'.freeze | |
# A request sent by the client to the server. | |
class RequestParser | |
INITIAL_BODY = '' | |
# Force external_encoding of request's body to ASCII_8BIT | |
INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode!) | |
# CGI-like request environment variables | |
attr_reader :env | |
# Unparsed data of the request | |
attr_reader :data | |
# Request body | |
attr_reader :body | |
def initialize | |
@parser = Thin::HttpParser.new | |
@data = '' | |
@nparsed = 0 | |
@body = StringIO.new(INITIAL_BODY.dup) | |
@env = { | |
RACK_INPUT => @body, | |
} | |
end | |
# Parse a chunk of data into the request environment | |
# Returns +true+ if the parsing is complete. | |
def parse(data) | |
if @parser.finished? # Header finished, can only be some more body | |
body << data | |
else # Parse more header using the super parser | |
@data << data | |
@nparsed = @parser.execute(@env, @data, @nparsed) | |
end | |
if finished? # Check if header and body are complete | |
@data = nil | |
@body.rewind | |
true # Request is fully parsed | |
else | |
false # Not finished, need more data | |
end | |
end | |
# +true+ if headers and body are finished parsing | |
def finished? | |
@parser.finished? && @body.size >= content_length | |
end | |
# Expected size of the body | |
def content_length | |
@env[CONTENT_LENGTH].to_i | |
end | |
# Returns +true+ if the client expect the connection to be persistent. | |
def persistent? | |
# Clients and servers SHOULD NOT assume that a persistent connection | |
# is maintained for HTTP versions less than 1.1 unless it is explicitly | |
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html) | |
if @env[HTTP_VERSION] == HTTP_1_0 | |
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP | |
# HTTP/1.1 client intends to maintain a persistent connection unless | |
# a Connection header including the connection-token "close" was sent | |
# in the request | |
else | |
@env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment