Skip to content

Instantly share code, notes, and snippets.

@matugm
Created September 30, 2020 00:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matugm/efe0a1c4fc53310f7ac93dcd1f041f6c to your computer and use it in GitHub Desktop.
Save matugm/efe0a1c4fc53310f7ac93dcd1f041f6c to your computer and use it in GitHub Desktop.
require 'socket'
class RequestParser
def parse(request)
method, path, version = request.lines[0].split
{
path: path,
method: method,
headers: parse_headers(request)
}
end
def parse_headers(request)
headers = {}
request.lines[1..-1].each do |line|
return headers if line == "\r\n"
header, value = line.split
header = normalize(header)
headers[header] = value
end
end
def normalize(header)
header.gsub(":", "").downcase.to_sym
end
end
class Response
attr_reader :code
def initialize(code:, data: "")
@response =
"HTTP/1.1 #{code}\r\n" +
"Content-Length: #{data.size}\r\n" +
"\r\n" +
"#{data}\r\n"
@code = code
end
def send(client)
client.write(@response)
end
end
class ResponseBuilder
SERVER_ROOT = "/tmp/web-server/"
def prepare(request)
if request.fetch(:path) == "/"
respond_with(SERVER_ROOT + "index.html")
else
respond_with(SERVER_ROOT + request.fetch(:path))
end
end
def respond_with(path)
if File.exists?(path)
send_ok_response(File.binread(path))
else
send_file_not_found
end
end
def send_ok_response(data)
Response.new(code: 200, data: data)
end
def send_file_not_found
Response.new(code: 404)
end
end
server = TCPServer.new('localhost', 8080)
loop {
client = server.accept
request = client.readpartial(2048)
request = RequestParser.new.parse(request)
response = ResponseBuilder.new.prepare(request)
puts "#{client.peeraddr[3]} #{request.fetch(:path)} - #{response.code}"
response.send(client)
client.close
}
@misterhtmlcss
Copy link

There are a few problems with this code:
if File.exists?(path) == X
if File.exist?(path) == 2714 FE0F

Also the SERVER_ROOT = "/tmp/web-server/" assumes the person knows to create a folder called /tmp/ and then /web-server/. The simple path would be to just do this:
SERVER_ROOT = ""
OR
SERVER_ROOT = "/"

The former works, but the latter won't the way you set up the code. Not a criticism just articulating that for people who see the the / and prefer it and don't realize there then needs to be other changes for that option to work.

Ahh last thing. You infer the creation of an HTML file and that this will then render it, but it won't actually render it. It'll present the code, but not the rendering of that code. The content_type needs to be added to the the initialize method and the @ response:

class Response
  attr_reader :code

  def initialize(code:, data: "", content_type: 'text/html') # content_type argument was added as well.
    @response =
    "HTTP/1.1 #{code}\r\n" +
    "Content-Type: #{content_type}\r\n" +.  #See this line is new
    "Content-Length: #{data.size}\r\n" +
    "\r\n" +
    "#{data}\r\n"
    
    @code = code
  end
  
  def send(client)
    client.write(@response)
  end
end

This was helpful and I learned a lot. The bug fixes were not a deterrent as it forced me to understand more and look up some things, which is why I sought out your article in the first place. Win win. Just adding this help in case someone is more junior and struggling more and how to proceed to solve the issues is less obvious to them. Blocking isn't good, struggling is good...at least I think that way.

Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment