Trying to understand an else clause without preceding conditional - ruby

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

Related

Ruby "include?" method not working

I have a few commands from a Twitch chat, it works and it's awesome! People can type commands like !about and it's going to send a response. There's just one problem, if you go down to the thread and look under # // COMMANDS // COMMANDS // COMMANDS. There's a problem, thereĀ“s a few admin commands (look under # ADMIN COMMANDS // ADMIN COMMANDS). I have this: if msg.include?("!project") then msg.slice!("!project "), but it's not working like I want it to. I want to be able to type !project Then some substring here. But when a substring is attached it doesn't work, even though I can print msg and it clearly includes !project. Both the !disconnect and !project command work when it's only those commands by themselves. Meaning that something like !disconnect Hello World wouldn't work even though it clearly includes !disconnect.
# Message formatting in console
class String
def red; "\e[31m#{self}\e[0m" end
def yellow; "\e[33m#{self}\e[0m" end
def green; "\e[32m#{self}\e[0m" end
def cyan; "\e[36m#{self}\e[0m" end
def bold; "\e[1m#{self}\e[22m" end
end
# Requied packages / modules
require 'socket'
require 'logger'
require 'open-uri'
# Create logger
File.delete("log.txt") # Clear previous log
log = Logger.new("log.txt", formatter: proc {|severity, datetime, progname, msg|
"#{datetime}: #{msg}\n"})
# Required Info
load "credentials.txt"
log.info("Loading \"credentials.txt\"")
# -------- IGNORE -------- #
OAUTH.downcase!
BOTNAME.downcase!
CHANNEL.downcase!.gsub!("#", "")
# //- AGE -// #
time = Time.new
age = time.year - 2000
if time.month == 10
if time.day < 28
age -= 1
end
elsif time.month < 10
age -= 1
end
# -------- IGNORE -------- #
# Save "Preparing to connect" to "log.txt"
log.info("Preparing to connect")
# Variables
socket = TCPSocket.new('irc.chat.twitch.tv', 6667)
send = "PRIVMSG ##{CHANNEL} :" # shortcut for sending messages
running = true
content = nil
message_count = 0
message_limit = Time.now.to_i
# Commands
commands = ["!about","!uptime","!commands","!cortexio","!followed"]
api_commands = ["!followed","!uptime"]
admin_commands = ["!disconnect","!project"]
# Authorization Login
socket.puts("PASS #{OAUTH}") # Send the password(oauth) to Twitch
socket.puts("NICK #{BOTNAME}") # Send the botname to Twitch
socket.puts("JOIN ##{CHANNEL}") # Send the channel to Twitch
# Save "Connected!" to "log.txt
log.info("Joining #{CHANNEL.capitalize} as #{BOTNAME.capitalize} using OAUTH Token: #{OAUTH[6,OAUTH.length-12]}" + "*"*12)
# Thread.abort_on_exception = true
# Loop (Background Thread) for recieving Twitch chat data
Thread.start do
socket.puts(send + "Connected!") # Send "Connected!" to the Twitch channel
puts "#{BOTNAME} Joined ##{CHANNEL}" # Connection Status
puts "You should be fully connected now" # Connection Status
puts ""
puts "Type \"clear\" to clear terminal"
puts ""
while (running) do
ready = IO.select([socket])
ready[0].each do |s|
line = s.gets
# Respond to Twitch IRC "PING" Message
if line.index("PING") == 0
line.strip!
socket.puts("PONG :tmi.twitch.tv\r\n")
log.info("[IRC Message]: " + line)
log.info("[IRC Response]: PONG :tmi.twitch.tv")
puts("-".bold.red*line.length)
puts "[Twitch] ".bold.cyan + "IRC: ".bold.yellow + line.bold.green
puts "[Response] ".bold.cyan + "IRC: ".bold.yellow + "PONG :tmi.twitch.tv".bold.green
puts("-".bold.red*line.length)
end
match = line.match(/^:(.+)!(.+)PRIVMSG ##{CHANNEL} :(.+)$/)
message = match && match[3]
if message =~ /^/
message.strip!
user = match[1] # Get username
# Twitch message limit - (Max 100 messages in 30 secs - Applies to mods and above)
# Avoid global ban
if Time.now.to_i - message_limit > 30 # If more than 30 seconds has passed
message_count = 0 # Reset "message_count"
end
if message_count == 0 # If "message_count" is 0
message_limit = Time.now.to_i # Start counting to 30 again
end
message_count = message_count + 1
end
# // COMMANDS // COMMANDS // COMMANDS
if message != nil
msg = message.downcase
# ADMIN COMMANDS // ADMIN COMMANDS
if admin_commands.include?(msg) and user == CHANNEL
if msg.include?("!disconnect")
socket.puts(send + "Disconnecting") # Disconnect from the channel
socket.puts("PART ##{CHANNEL}") # Disconnect from the channel
Disconnect()
log.info("[Command] #{user}: #{message}")
elsif msg.include?("!project")
msg.slice!("!project ")
File.write("Responses/project.txt", msg)
end
user = user.capitalize # Capitalize first letter (Cuz I'm that kind of person)
elsif commands.include?(msg) and message_count < 80
puts "[Command] ".bold.cyan + "#{user}: ".bold + "#{message}".bold.cyan
# API COMMANDS // API COMMANDS
if api_commands.include?(msg)
if msg.include?("!uptime")
file = open("https://decapi.me/twitch/uptime?channel=#{CHANNEL}")
content = "#{CHANNEL} has been streaming for: " + file.read
elsif msg.include?("!followed")
file = open("https://decapi.me/twitch/followage/#{CHANNEL}/#{user}")
content = file.read
if content == "Follow not found"
content = "#{user} is not following #{CHANNEL}"
else
content = "#{user} has been following #{CHANNEL} for " + content
end
end
puts "[Response] ".bold.red + "Cortexio: ".bold + "API: ".bold.yellow + "\"#{content}\"".bold.red
else
file = open "Responses/" + msg.gsub!("!","") + ".txt" # open matching file
content = file.read
file.close
puts "[Response] ".bold.red + "Cortexio: ".bold + "File: ".bold.yellow + "\"#{msg}.txt\"".bold.red
end
file.close
log.info("[Command] #{user}: #{message}")
else
puts "[Message] ".bold.green + "#{user}: ".bold + "#{message}".bold.green
log.info("[Message] #{user}: #{message}")
if message[0] == "!" # Unrecognized command
content = "Unrecognized command: \"#{message}\" - Type !commands to see a list of available commands."
end
end
# Response handling
if content != nil
content.gsub!("USER", "##{user}")
content.gsub!("AGE", "#{age}")
content.gsub!("CHANNEL", "#{CHANNEL}")
if content.include?("COMMANDS")
content.gsub!("COMMANDS", "#{commands}")
content.gsub!("\"", "")
content.gsub!("[","")
content.gsub!("]","")
end
socket.puts(send + content) # Send response if any
content = nil # Too avoid multiple messages with the same response
end
end
end
end
end
def Disconnect() # End script
running = false
exit
end
# Loop to keep bot going
while (running) do
input = gets.chomp
if input == "clear"
system "clear" or system "cls"
end
end
The problem isn't with
if msg.include?("!project")
it's with the line before it:
admin_commands.include?(msg)
admin_commands is an array which contains the 2 strings ["!disconnect","!project"]. It does not, however, contain the string "!project Then some substring here", so you are never checking if msg.include?("!project"). You should be seeing your log messages about an unrecognized command, which means you aren't making it into your first if statement. What you'll want to be doing is something like:
if admin_commands.any? { |command| msg.include?(command) }
if msg.include?("!project")
# ...

removing code in ruby used in multiple class methods

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

Ruby Mechanize Stops Working while in Each Do Loop

I am using a mechanize Ruby script to loop through about 1,000 records in a tab delimited file. Everything works as expected until i reach about 300 records.
Once I get to about 300 records, my script keeps calling rescue on every attempt and eventually stops working. I thought it was because I had not properly set max_history, but that doesn't seem to be making a difference.
Here is the error message that I start getting:
getaddrinfo: nodename nor servname provided, or not known
Any ideas on what I might be doing wrong here?
require 'mechanize'
result_counter = 0
used_file = File.open(ARGV[0])
total_rows = used_file.readlines.size
mechanize = Mechanize.new { |agent|
agent.open_timeout = 10
agent.read_timeout = 10
agent.max_history = 0
}
File.open(ARGV[0]).each do |line|
item = line.split("\t").map {|item| item.strip}
website = item[16]
name = item[11]
if website
begin
tries ||= 3
page = mechanize.get(website)
primary1 = page.link_with(text: 'text')
secondary1 = page.link_with(text: 'other_text')
contains_primary = true
contains_secondary = true
unless contains_primary || contains_secondary
1.times do |count|
result_counter+=1
STDERR.puts "Generate (#{result_counter}/#{total_rows}) #{name} - No"
end
end
for i in [primary1]
if i
page_to_visit = i.click
page_found = page_to_visit.uri
1.times do |count|
result_counter+=1
STDERR.puts "Generate (#{result_counter}/#{total_rows}) #{name}"
end
break
end
end
rescue Timeout::Error
STDERR.puts "Generate (#{result_counter}/#{total_rows}) #{name} - Timeout"
rescue => e
STDERR.puts e.message
STDERR.puts "Generate (#{result_counter}/#{total_rows}) #{name} - Rescue"
end
end
end
You get this error because you don't close the connection after you used it.
This should fix your problem:
mechanize = Mechanize.new { |agent|
agent.open_timeout = 10
agent.read_timeout = 10
agent.max_history = 0
agent.keep_alive = false
}

Ruby output is not displayed on the sinatra browser

I want to bulid a multi threaded application. If i do not use threads, everything works fine. When i try to use threads, then nothing is displayed on the browser. when i use the syntax 'puts "%s" %io.read' then it displays on the command prompt and not on the browser. Any help would be appreciated.
require 'sinatra'
require 'thread'
set :environment, :production
get '/price/:upc/:rtype' do
Webupc = "#{params[:upc]}"
Webformat = "#{params[:rtype]}"
MThread = Thread.new do
puts "inside thread"
puts "a = %s" %Webupc
puts "b = %s" %Webformat
#call the price
Maxupclen = 16
padstr = ""
padupc = ""
padlen = (Maxupclen - Webupc.length)
puts "format type: #{params[:rtype]}"
puts "UPC: #{params[:upc]}"
puts "padlen: %s" %padlen
if (Webformat == 'F')
puts "inside format"
if (padlen == 0 ) then
IO.popen("tstprcpd.exe #{Webupc}")
{ |io|
"%s" %io.read
}
elsif (padlen > 0 ) then
for i in 1 .. padlen
padstr = padstr + "0"
end
padupc = padstr + Webupc
puts "padupc %s" %padupc
IO.popen("tstprcpd.exe #{padupc}") { |io|
"%s" %io.read
}
elsif (padlen < 0 ) then
IO.popen("date /T") { |io|
"UPC length must be 16 digits or less." %io.read
}
end
end
end
end
Your code has several problems:
It is not formatted properly
You are using Uppercase names for variables; that makes them constants!
puts will not output to the browser, but to the console. The browser will recieve the return value of the block, i.e. the return value of the last statement in the block. Therefore, you need to build your output differently (see below).
You are never joining the thread
Here's a minimal sinatra app that uses a thread. However, the thread makes no sense in this case because you must wait for its termination anyway before you can output the result to the browser. In order to build the output I have used StringIO, which you can use with puts to build a multiline string conveniently. However, you could also simply initialize res with an empty string with res = "" and then append your lines to this string with res << "new line\n".
require 'sinatra'
require 'thread'
require 'stringio'
get '/' do
res = StringIO.new
th = Thread.new do
res.puts 'Hello, world!'
end
th.join
res.string
end

Why won't my server open files?

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

Resources