I've been working on this code and for some reason the server can't open the index file, and I can't understand why. I've checked other people's code and there doesn't look like there is a difference.
Here is my code:
require 'socket'
class Server
def initialize (base, request, server_name, session, charset)
#base = base
#request = request
#charset = charset
#server_name = server_name
#session = session
serve()
end
def serve ()
access_log()
getAddress()
#contentType = getContentType()
#session.print "HTTP/1.1 200 OK\r\nServer: #{#server_name}\r\nContent-Type: #{#contentType}; charset=#{charset}\r\n\r\n"
getFile()
#base = nil
#request = nil
#server_name = nil
#contentType
#session.close
puts "Session Ended\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
#session = nil
end
def access_log ()
log = File.open(#base + "data/access_log.txt", "w")
log.puts "#{Time.now.localtime.strftime("%Y/%m/%d %H:%M:%S")} #{#session.peeraddr[2]}:#{#session.peeraddr[1]} #{#request}" unless log == nil
log.close
log = nil
end
def getAddress ()
#src = #request
#src.gsub!(/GET /, '').gsub!(/ HTTP.*/, '')
#src.strip
#src = #base + "root" + #src
#src.gsub!('/', '\\')
end
def getContentType ()
ext = nil
ext = File.extname(#src)
return "text/html" if ext == ".html" or ext == ".htm"
return "text/plain" if ext == ".txt"
return "text/css" if ext == ".css"
return "image/jpeg" if ext == ".jpeg" or ext == ".jpg"
return "image/gif" if ext == ".gif"
return "image/bmp" if ext == ".bmp"
return "text/plain" if ext == ".rb"
return "text/xml" if ext == ".xml"
return "text/xml" if ext == ".xsl"
#return "image/x-icon" if ext == ".ico" or ext == ".icon"
return "text/html"
end
def getFile ()
begin
if !File.exist?(#src)
puts "File: #{#src} could not be found"
if #contentType.include?("image")
file = File.open(#base + "root/server_files/broken_image.png", "r").each do |code|
#session.puts code
end
else
file = File.open(#base + "root/server_files/error404.html", "r").each do |code|
#session.puts code
end
end
else
puts "File #{#src} was opened"
file = File.open(#src, "r").each do |code|
#session.puts code
end
end
ensure
file.close unless file == nil
end
end
end
base = "C:\\Base\\"
server_name = "Some Server"
host = "localhost"
port = 80
charset = "UFT-8"
server = TCPServer.new(host, port)
puts "~ Server hosted on #{host}:#{port} ~\n====================================\n"
loop {
Thread.new(server.accept) do |session|
request = session.gets
puts "#{session.peeraddr[2]}:#{session.peeraddr[1]} #{request}"
Server.new(base, request, server_name, session, charset)
end
}
p = gets.chomp
server.close
There are problems with the code. I don't know if these directly cause the problem, but there's enough code smell that the following untested changes might help.
Don't use CamelCase method names in Ruby. We use snake_case.
require 'socket'
class Server
def initialize(base, request, server_name, session, charset)
#base = base
#request = request
#charset = charset
#server_name = server_name
#session = session
serve()
end
def serve
access_log
get_address()
#content_type = get_content_type()
#session.print "HTTP/1.1 200 OK\r\nServer: #{#server_name}\r\nContent-Type: #{#content_type}; charset=#{charset}\r\n\r\n"
get_file()
#content_type
#session.close
puts "Session Ended\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
#base = #request = #server_name = #session = nil
end
I don't know why you have #content_type on its own line prior to #session.close. It's not a method call, nor does it look like it's going to return anything to the server or the outgoing data-stream.
def access_log
File.open(File.join(#base, "data/access_log.txt"), "w") do |log|
log.puts "#{Time.now.localtime.strftime("%Y/%m/%d %H:%M:%S")} #{#session.peeraddr[2]}:#{#session.peeraddr[1]} #{#request}"
end
end
Ruby's File.open takes a block. When the block exits the file will be closed automatically. File.join is the proper way to build a path. Ruby is aware of the proper path delimiters and will use them automatically, helping to make your code more portable.
def get_address
src = #request.gsub(/GET /, '').gsub(/ HTTP.*/, '').strip
#src = (#base + "root" + src).gsub('/', '\\')
end
You're doing a lot of gsub! for no reason. Chain them, combine the string, do a final gsub and move on.
def get_content_type()
ext = File.extname(#src)
content_type = case ext
when /\.html?/
"text/html"
when ".txt"
"text/plain"
when ".css"
"text/css"
when /\.jpe?g/
"image/jpeg"
when ".gif"
"image/gif"
when ".bmp"
"image/bmp"
when ".rb"
"text/plain"
when /\.x[ms]l/
"text/xml"
else
"text/html"
end
content_type
end
It's confusing to have multiple returns in a sub-routine or method, so this cleans it up. The final content_type will be returned as the result of the method call. Using case/when lets you use multiple tests for each when, reducing line-noise.
def get_file()
begin
if !File.exist?(#src)
puts "File: #{#src} could not be found"
if #content_type["image"]
File.open(File.join(#base, "root", "server_files", "broken_image.png"), "rb") do |image|
#session.puts image.read
end
else
File.foreach(File.join(#base, "root", "server_files", "error404.html")) do |li|
#session.puts li
end
end
else
puts "File #{#src} was opened"
File.foreach(#src) do |li|
#session.puts li
end
end
rescue Exception => e
puts e.message
end
end
When you want to read a binary file, such as "broken_image.png", ALWAYS open it in binary mode: "rb". Otherwise, Ruby will assume it's OK to translate line-endings to the native format for the OS, which would corrupt the data. Also, again, use the block-forms of the File methods so they automatically close. And, use File.join for portability. When you're reading a text file, use File.foreach to read it line-by-line, unless you KNOW it will fit into the available memory; "Slurping" your files is bad form because it isn't scalable.
end
base = "C:/Base/"
server_name = "Some Server"
host = "localhost"
port = 80
charset = "UFT-8"
server = TCPServer.new(host, port)
puts "~ Server hosted on #{host}:#{port} ~\n====================================\n"
loop {
Thread.new(server.accept) do |session|
request = session.gets
puts "#{session.peeraddr[2]}:#{session.peeraddr[1]} #{request}"
Server.new(base, request, server_name, session, charset)
end
}
p = gets.chomp
server.close
The code changes are available at: https://gist.github.com/anonymous/6515451
Related
This code from Handle Web Requests with Ruby appears in the video demo and runs in the video, but I don't see how it could work. The cookie_parts "block" ends with an else clause that has no preceding if or rescue.
require 'socket'
server = TCPServer.new 8000
PAGES = {
"/" => "Hi, welcome to the home page!",
"/about" => "About us: we are http hackers",
"/news" => "We haven't made much news yet with this server, but stay tuned"
}
PAGE_NOT_FOUND = "Sorry, there's nothing here."
loop do
session = server.accept
request = []
while (line = session.gets) && (line.chomp.length > 0)
request << line.chomp
end
puts "finished reading"
http_method, path, protocol = request[0].split(' ') # 3 parts to request line
cookie_header = request.detect { |line| line =~ /^Cookie: / }
cookie_parts = cookie_header.sub /^Cookie: /, ''
cookie_parts = cookie_parts.split('; ')
cookie_parts = cookie_parts.map { |cookie| cookie.split('=') }
cookies = Hash[*cookie_parts.flatten]
else
cookies = {}
end
cookies["session_key"] ||= Time.now.to_f
# nil.to_i returns 0, so if cookie isn't set, count will be 0
count = cookies["session_count"].to_i
count += 1
cookies["session_count"] = count
if PAGES.keys.include?(path)
status = "200 OK"
response_body = PAGES[path]
else
status = "404 Not Found"
response_body = PAGE_NOT_FOUND
end
session.puts <<-HEREDOC
HTTP/1.1 #{status}
set-cookie:session_key=#{cookies["session_key"]}
set-cookie:session_count=#{cookies["session_count"]}
#{response_body}
This was visit number #{cookies["session_count"]}!
HEREDOC
session.close
end
Running the same code locally yields the errors I'd expect:
server.rb:28: else without rescue is useless
server.rb:57: syntax error, unexpected `end', expecting end-of-input
What conditional or block is the else supposed to belong to?
Watch the rest of the video, to wit, at ~5:30 you'll see the missing if I mentioned in my comment:
if cookie_header = request.detect { |line| line =~ /^Cookie: / }
cookie_parts = cookie_header.sub /^Cookie: /, ''
cookie_parts = cookie_parts.split('; ')
cookie_parts = cookie_parts.map { |cookie| cookie.split('=') }
cookies = Hash[*cookie_parts.flatten]
else
cookies = {}
end
In the middle of the code in your question, there is this section
cookie_header = request.detect { |line| line =~ /^Cookie: / }
cookie_parts = cookie_header.sub /^Cookie: /, ''
cookie_parts = cookie_parts.split('; ')
cookie_parts = cookie_parts.map { |cookie| cookie.split('=') }
cookies = Hash[*cookie_parts.flatten]
else
cookies = {}
end
There you have an else block but there is no if condition in front of it. Ruby doesn't know how to handle it and therefore the error message.
I agree that the error message is a bit misleading because an else block like that is more likely missing an if condition than a rescue. But a rescue block is also an example in which Ruby allows an else like this:
begin
# code that might raise an error
rescue
# code that is run when there was an error
else
# code that is run when there was _no_ error
end
My code creates a github gist using the github API. After creating the gist, the API returns a status code to me. If the code is "201" the gist was created. But if it is "400" there was an error. In my code the variable that saves that state is response_status
This is my code:
require 'net/http'
require 'json'
require 'uri'
class Gist
attr_reader :response_status
attr_reader :try_again
def initialize(filename,description,state,content)
#filename = filename
#description = description
#state = state
#content = content
end
def post(uri, request)
request.basic_auth("my_username", "my_token")
request.body = JSON.dump({
"description" => #description,
"public" => #state,
"files" => {
#filename => {
"content" => #content
}
}
})
req_options = { use_ssl: uri.scheme == "https" }
begin
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
json = response.body
parsed = JSON.parse(json)
#response_status = "#{ response.code }"
if #response_status == "201"
puts "Tu gist se ha creado con exito. La URL de su gist es: "+ parsed["url"]
end
rescue SocketError => se
puts "Ha ocurrido un error de conexión. Quiere intentar nuevamente?"
#try_again = gets.chomp.capitalize
end
end
end
loop do
filename= gets.chomp
if File.exist?("#{filename}")
description = gets.chomp
state = gets.chomp.capitalize
if state == "Si"
state = true;
elsif state == "No"
state = false;
end
open(filename, "r") { |file| #contenido = file.read() }
content = #contenido
uri = URI.parse("https://api.github.com/gists")
request = Net::HTTP::Post.new(uri)
gist = Gist.new(filename,description,state,content)
gist.post(uri, request)
break if gist.response_status == "201"
break if gist.try_again == "No"
else
puts "The file does not exist"
continue = gets.chomp.capitalize
break if continue == "No"
end
end
I want to test test cases using rspec, but I did not understand.
It occurred to me as a test case that a Gist be created. For example, I thought about checking if the variable that returns the state of the Gist is equal to "201", but that didn't work for me.
This is my rspec file:
require './git_clases.rb'
RSpec.describe Gist do
describe "#Gist" do
it "#Gist created" do
expect(response_status)==("201")
end
end
end
response_status is a method on your Gist object. You need to create a Gist object, call post on it, and check response_status on your object.
RSpec.describe Gist do
# Generally organize by method
describe "#post" do
# Example descriptions read grammatically correct
# "Gist #post creates a gist"
it "creates a gist" do
filename = "test.txt"
description = "testing gist"
state = "dunno what goes here"
content = "some test content"
gist = described_class.new(filename, description, state, content)
uri = URI.parse("https://api.github.com/gists")
request = Net::HTTP::Post.new(uri)
gist.post(uri, request)
expect(gist.response_status).to eq 201
end
end
end
described_class is Gist from RSpec.describe Gist. It's preferable to hard coding the class name into the example in case that example becomes shared or the class name changes.
expect(gist.response_status).to eq 201 is really expect(gist.response_status).to( eq(201) ). This compares gist.response_status using the matcher eq(201) which just checks that it equals 201. This might seem a bit much for a simple equality check, but it allows rspec to provide a wide variety of complex matchers as well as custom ones.
Your post method is a bit odd in that the user is expected to initialize and pass in the URL and Request object. That should probably be done by Gist.
There is no need to turn the response_status into a string. It's preferable to leave it as an Integer. If you do need it as a string, use response.code.to_s.
I have the following, which is repeated four times:
def self.curl_delete(url, response_flag)
url = "#{#baseUrl}#{url}"
curl_response = Curl.delete(url) do |curl|
curl.headers['Content-Type'] = 'text/plain'
curl.headers['Client'] = 'Curb DELETE'
curl.headers['Authorization'] = "Bearer #{#token}"
curl.verbose = false
end
response = JSON.parse(curl_response.body_str)
if response_flag == true
BF.puts_and_file " *response flag set to true for DELETE*\n"
BF.puts_and_file " url: #{url}\n"
BF.puts_and_file " response: #{response}\n\n"
end
return response
end
I would like to turn that if statement into a method.
I tried def response_flag_true(flag) but didn't work...what's the right way to DRY up that code?
Keep it simple:
def bf_puts_and_file(indent, *messages)
messages.each { |s| BF.puts_and_file(' ' * indent + s) }
end
Used as follows:
bf_puts_and_file(5, "*response flag set to true for DELETE*\n",
"url: #{url}\n",
"response: #{response}\n\n") if response_flag
I'm trying to define methods to parse through an apache log file and pull ip addresses, URLs, requests per hour, and error codes. I've got everything working outside of methods, but when attempting to put that code into the methods I keep getting the error message "Stack level too deep." Here is the code in question.
class CommonLog
def initialize(logfile)
#logfile = logfile
end
def readfile
#readfile = File.readlines(#logfile).map { |line|
line.split()
}
#readfile = #readfile.to_s.split(" ")
end
def ip_histogram
#ip_count = 0
#readfile.each_index { |index|
if (#readfile[index] =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ )
puts #readfile[index]
puts #ip_count += 1
end
}
end
def url_histogram
url_count = 0
cleaned_file.each_index { |index|
if (cleaned_file[index] =~ /\/{1}(([a-z]{4,})|(\~{1}))\:{0}\S+/ )
puts cleaned_file[index]
puts url_count += 1
end
}
end
def requests_per_hour
end
def sorted_list
end
end
my_file = CommonLog.new("test_log")
cleaned_file = my_file.readfile
puts cleaned_file.ip_histogram
It looks like the problem lies on you CommonLog#readfile method:
def readfile
#readfile = File.readlines(#logfile).map { |line|
line.split()
}
#readfile = readfile.to_s.split(" ")
end
Notice that inside the implementation of readfile your calling readfile recursively? When it executes it reads the lines from the file, maps them and assign the result the #readfile; then it calls readfile and the method starts to execute again; this goes on forever, until you stack blows up because of too many recursive method calls.
I assume what you actually meant is:
#readfile = #readfile.to_s.split(" ")
Has anyone tried streaming html/text/content from webrick? I've tried assigning an IO to the response body, but webrick is waiting for the stream to be closed first.
found this link by accident (http://redmine.ruby-lang.org/attachments/download/161) which contains webrick patch
# Copyright (C) 2008 Brian Candler, released under Ruby Licence.
#
# A collection of small monkey-patches to webrick.
require 'webrick'
module WEBrick
class HTTPRequest
# Generate HTTP/1.1 100 continue response. See
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/18459
def continue
if self['expect'] == '100-continue' && #config[:HTTPVersion] >= "1.1"
#socket.write "HTTP/#{#config[:HTTPVersion]} 100 continue\r\n\r\n"
#header.delete('expect')
end
end
end
class HTTPResponse
alias :orig_setup_header :setup_header
# Correct termination of streamed HTTP/1.1 responses. See
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/18454 and
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/18565
def setup_header
orig_setup_header
unless chunked? || #header['content-length']
#header['connection'] = "close"
#keep_alive = false
end
end
# Allow streaming of zipfile entry. See
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/18460
def send_body(socket)
if #body.respond_to?(:read) then send_body_io(socket)
elsif #body.respond_to?(:call) then send_body_proc(socket)
else send_body_string(socket)
end
end
# If the response body is a proc, then we invoke it and pass in
# an object which supports "write" and "<<" methods. This allows
# arbitary output streaming.
def send_body_proc(socket)
if #request_method == "HEAD"
# do nothing
elsif chunked?
#body.call(ChunkedWrapper.new(socket, self))
_write_data(socket, "0#{CRLF}#{CRLF}")
else
size = #header['content-length'].to_i
#body.call(socket) # TODO: StreamWrapper which supports offset, size
#sent_size = size
end
end
class ChunkedWrapper
def initialize(socket, resp)
#socket = socket
#resp = resp
end
def write(buf)
return if buf.empty?
data = ""
data << format("%x", buf.size) << CRLF
data << buf << CRLF
socket = #socket
#resp.instance_eval {
_write_data(socket, data)
#sent_size += buf.size
}
end
alias :<< :write
end
end
end
if RUBY_VERSION < "1.9"
old_verbose, $VERBOSE = $VERBOSE, nil
# Increase from default of 4K for efficiency, similar to
# http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/branches/ruby_1_8/lib/net/protocol.rb?r1=11708&r2=12092
# In trunk the default is 64K and can be adjusted using :InputBufferSize,
# :OutputBufferSize
WEBrick::HTTPRequest::BUFSIZE = 16384
WEBrick::HTTPResponse::BUFSIZE = 16384
$VERBOSE = old_verbose
end
to use simply pass a proc to as the response body, like so
res.body = proc { |w|
10.times do
w << Time.now.to_s
sleep(1)
end
}
woot!
I would suggest against using WEBrick for anything really, it's junk. I would say try Mongrel.
I know that wasn't your question, it's just some friendly advice.