TCPServer: show exact request in ruby - ruby

I'm trying to see the exact request incl. potential headers like IP, mac address etc.
Server side code:
require 'socket'
server = TCPServer.new 2000
loop do
client = server.accept
puts client.inspect # This should show IP and all transmitted information
client.close
end
Output is #<TCPSocket:fd 8>, but should show the exact request

You can read what client send to the server like this:
require 'socket'
server = TCPServer.new 2000
loop do
client = server.accept
# Print whatever client sends to server
while line = client.gets
puts line
end
client.close
end
The request header is not stored inside client, it works slightly differently: when your TCP server gets client request, you must accept it and then read whatever client is sending to server.

Related

Clarification of the Ruby Socket Library Gets Method

I am working on an assignment where I have to develop a web server in Ruby using the socket library. I was able to get a simple web server up and running as seen in this thread here .
I am currently working on getting and storing the body of an HTTP request into a variable in my web server. The problem I am running into is trying to define a while loop that gets the entire body of a HTTP request.
I am attempting to get the body of a HTTP request by using the gets method. I could not find any documentation on this method (I saw it being used here)
and was wondering if there were more documentation online.
In my first post here, someone suggested that I use the Content-Length header to determine the size of the body and how much data to read from the socket. I don't really understand how I would go about implementing this because I am unsure how the gets method functions.
Since this is for an assignment, I don't think posting code would be a good idea. I am looking for more information on the gets method and any tips to point me towards the right direction.
You shouldn't be using gets. gets tries to read complete lines (ie it reads up to a line separator), but there is no guarantee that an http request body ends with a line separator.
Instead you should be using read - this allows you to read an arbitrary amount of data (as you mentioned you can use the content length header to know how much to read)
Your ultimate problem isn't related to gets, or even really anything in your code. But before we get to that, let's answer this question & explore sockets a little bit.
If you follow the chain up, you find that Ruby's TCPSocket class inherits from its IO class. It's IO that provides gets. gets will read, line-by-line, until there's nothing more to read. Let's create a simple client that connects to a port, spits out 4 lines of poetry, and then quits:
# poetry_sender.rb
require 'socket'
poem = ["'God save thee, ancient Mariner!",
"From the fiends, that plague thee thus!—",
"Why look'st thou so?'—With my cross-bow",
"I shot the ALBATROSS."]
puts "Client establishing connection..."
s = TCPSocket.new 'localhost', 2000
puts "Client sending poetry..."
poem.each { |line| s.puts line } # Print each line out on the socket
s.close # Close our socket
puts "All done."
And a simple server, that displays what the client sends us:
# poetry_receiver.rb
require 'socket'
server = TCPServer.new 2000 # Server bind to port 2000
loop do
puts "Server now awaiting some poetry..."
socket = server.accept # Wait for a client to connect
while line = socket.gets
puts "A client sent us this beautiful line: #{line}"
end
puts "They had nothing more to say; let's disconnect them."
socket.close
end
If you run the server (poetry_receiver.rb) first, and then the client, you'll see some output like this:
Server now awaiting a connection...
A client sent us this beautiful line: 'God save thee, ancient Mariner!
A client sent us this beautiful line: From the fiends, that plague thee thus!—
A client sent us this beautiful line: Why look'st thou so?'—With my cross-bow
A client sent us this beautiful line: I shot the ALBATROSS.
They had nothing more to say; let's disconnect them.
Server now awaiting a connection...
The last two lines are the important ones; they indicate that socket.gets returned nil and we exited the while loop.
So, how can we modify our poetry_sender.rb so the server doesn't detect the end of the poem? You might think it's got something to do with blank lines, but if you set poem = [] or poem = ["", "", ""] then you'll find that it still gets disconnected OK. But what if we added a delay before closing the socket in poetry_sender.rb?
sleep 60
s.close # Close our socket
puts "All done."
Now you'll see a big delay in the server output. The TCP server doesn't break out of its while loop until the TCP client closes its socket.
Now we can turn to your broader problem: you're trying to implement a simple HTTP server, but your server is getting hung up in a while loop when you try to connect via your web browser. It's because your web browser is keeping that socket open; but it has to, otherwise it has no way to send you back a response. So, how do we know when a client has finished sending us a response? The HTTP 1.1 spec says:
A client sends an HTTP request to a server in the form of a request message... followed by header fields... an empty line to indicate the end of the header section, and finally a message body containing the payload body (if any).
Let's not worry about the message body; how could we write a while loop that terminates if it has no more impact, or if it receives a blank line? Here's one way, in a simple HTTP server that just sends back "Hello world" no matter what request it receives:
require 'socket'
server = TCPServer.new('localhost', 2345)
http_request = [] # We'll store the lines of our incoming request here.
loop do
socket = server.accept
while (line = socket.gets) && line.chomp != '' # While the client is connected, and hasn't sent us a blank line yet...
http_request << line
end
# Send response headers
socket.print "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n"
# Send response body
socket.print "Hello world!"
socket.close
end
Quite late to the party, but I'm currently implementing my own rack app server (for fun).
Here you can see how I do it: https://github.com/tak1n/reifier/blob/master/lib/reifier/request.rb
The first line of a HTTP request is always the request line, which is basically something like GET /test HTTP/1.1
After the request line until \r\n you get the headers.
After that you are able to read the body (if PUT / POST request) with just using the CONTENT_LENGTH you parsed from the headers.

Ruby Socket Programming Waiting for Client Input

I am trying to implement a small script that will create a server, wait for a client to connect, create a new thread for the client and then process client requests. The first request from the client will be a HELO text and the server must respond with the HELO text followed by IP Address, Port Number and a Student Number. I have got this bit working.
The next request will be any random string and this must not return anything. I have got this bit working.
The last request will be a KILL_SERVICE request which must close the server. I am not sure how to do this. The other issue is that my program will only work with one command at a time. I do not know how to deal with one command and then wait for the next command from the client. My server code is below. Any help would be appreciated.
require 'socket'
port = 8888
puts "Starting Up Server"
server = TCPServer.open(port)
while (client = server.accept)
Thread.start do
input = client.gets
if input.start_with?("HELO")
client.puts "#{input}IP:#{client.peeraddr[2]}\nPort:#{port}\nStudentID:[2]\n"
elsif input == "KILL_SERVICE\n"
client.puts "KILL"
else
puts input
end
end
end
As soon as your code leaves the Thread.start block that thread is considered finished. If you want it to stick around you need to wrap it in a loop:
while (client = server.accept)
Thread.start do
client_running = true
while (client_running)
# ... Your code
end
end
end
Then, if any of those commands should stop the thread, set client_running = false and the loop will exit. To force shut-down the whole thing, exit(0) will end the process.
Update: Added block on how to handle shutting down the server itself, not just the connection.
You need to continue waiting for input from the client after accepting the connection.
while (client = server.accept)
Thread.start do
while input = client.gets
# process input
end
end
end
When you receive KILL_SERVICE you need to close the client using client.close and break out of the while loop using break. Once you've broken out of the while loop, the thread handling that client will exit.

The UDPSocket in ruby

I am new in ruby,and follow the book "The Ruby Programming Language", I am trying to learn some Socket in ruby,and the following is my simple server/client:
## server
require 'socket'
server= UDPSocket.new
server.bind('localhost', 3000)
loop do
data,address=server.recvfrom(1024)
server.send(data.reverse,0,address[3],address[1]) ############ My problem #########
puts "get #{data} from #{address[3]}"
end
##client
require 'socket'
ds = UDPSocket.new
#ds.connect('localhost', 3000)
while line=gets
ds.send(line.chomp, 0,'localhost', 3000)
response,address = ds.recvfrom(1024)
puts response
end
Note the line
server.send(data.reverse,0,address[3],address[1])
If I comment this line,it seems that the server will hold on ,and do not response to the client anymore.
I wonder why?
Does it mean that the UDPSocket must do some response to the client to continue?
Because you write "recvfrom" in the server side, if you comment this, it will not block, and it will continue to send data to client side. However, in real situation, the peers of communication should exchange information.

Check if socket-client is still connected

I want to implement a while-loop that runs as long as a Client is connected at my socket.
It's gonna be Thread based so i want to make shure that the Thread gets closed once the Client disconnected.
question: How do I check if the Client is still connected to my socket?
question: Is the Thread already getting closed when connection closes if I startet it like this: Thread.start(socket.accept) do |client| ...
Check this out,
require 'socket'
th=[]
server = TCPServer.open(2000)
loop do
Thread.start(server.accept) do |client|
client.puts "server started.."
# Something
th = Thread.list
th.each do |t|
if t.status == "sleep" # checks all client status
f = t.kill # kills thread if client is disconnected
end
end
end
end
You may kill the thread or you can store the IP addres of clients using that thread in an array and make a compare with that for exact client.(like, t.addr)
I don't have experience programming ruby. If it was .Net, what you pretend is not possible without some kind of keep-alive protocol. If the server does not get data from client after x seconds it assumes the client disconnected. The client must have a timer to send something to the server when not communicating.
server = TCPServer.open 12345
loop {
Thread.start(server.accept) do |client|
...
puts "client is offline!" if client.closed?
...
client.close
end
}
You may check "closed?" in "client".

How to prevent "The connection was reset" error?

I have a very basic TCP server implemented in Ruby. In general it does what it's supposed to, but every once in a while I get "The connection to the server was reset while the page was loading" error. I have a feeling that it has something to do with close terminating the connection too soon. If so, how do I wait for all the data to be sent? Or is it something else?
require 'socket'
server = TCPServer.new('', 80)
loop do
session = server.accept
begin
session.print Time.now
ensure
session.close
end
end
I'm not an expert in this area, but here is what I believe is happening....
The browser sends a GET request with the header field "Connection: keep-alive". So the browser is expecting to keep the connection alive at least until it receives a complete chunk of the response. Under this protocol, the server response must include a header specifying the length of the response, so that the browser knows when it has received the complete response. After this point, the connection can be closed without the browser caring.
The original example closes the connection too quickly, before the browser can validate that a complete response was received. Curiously, if I run that example and refresh my browser several times, it will load about every 1 in 10 tries. Maybe this erratic behavior is due to the browser occasionally executing fast enough to beat my server closing the connection.
Below is a code example that executes consistently in my browser:
require 'socket'
response = %{HTTP/1.1 200 OK
Content-Type: text;charset=utf-8
Content-Length: 12
Hello World!
}
server = TCPServer.open(80)
loop do
client = server.accept
client.puts response
sleep 1
client.close
end
I suspect it's because the browser is expecting an HTTP response with headers &c. Curiously, you can make the "reset" error happen every time if you put before the "ensure" a sleep of, say, one second.
How to fix it depends upon what you are after. If this is not to be an HTTP server, then don't use the browser to test it. Instead, use telnet or write a little program. If it is to be an HTTP server, then take a look at webrick, which is built into Ruby MRI >= 1.8. Here's how:
#!/usr/bin/ruby1.8
require 'webrick'
# This class handles time requests
class TimeServer < WEBrick::HTTPServlet::AbstractServlet
def do_GET(request, response)
response.status = 200
response['Content-Type'] = 'text/plain'
response.body = Time.now.to_s
end
end
# Create the server. There are many other options, if you need them.
server = WEBrick::HTTPServer.new(:Port=>8080)
# Whenever a request comes in for the root page, use TimeServer to handle it
server.mount('/', TimeServer)
# Finally, start the server. Does not normally return.
server.start
Also, should note that including Connection: close in the response header doesn't seem to help me at all with this connection reset error in my browser (FFv3.6). I have to include both the content-length header field, and include the sleep method to put some delay in the connection closing in order to get a consistent response in my browser.

Resources