I am trying to create a simple WebSocket connection in JavaScript against my Rails app. I get the following:
WebSocket connection to 'ws://localhost:4000/' failed: Error during WebSocket handshake: 'Sec-WebSocket-Accept' header is missing
What am I doing wrong? Here is my code:
JavaScript:
var socket = new WebSocket('ws://localhost:4000');
socket.onopen = function() {
var handshake =
"GET / HTTP/1.1\n" +
"Host: localhost\n" +
"Upgrade: websocket\n" +
"Connection: Upgrade\n" +
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\n" +
"Sec-WebSocket-Protocol: quote\n" +
"Sec-WebSocket-Version: 13\n" +
"Origin: http://localhost\n";
socket.send(handshake);
};
socket.onmessage = function(data) {
console.log(data);
};
Ruby:
require 'rubygems'
require 'em-websocket-server'
module QuoteService
class WebSocket < EventMachine::WebSocket::Server
def on_connect
handshake_response = "HTTP/1.1 101 Switching Protocols\n"
handshake_response << "Upgrade: websocket\n"
handshake_response << "Connection: Upgrade\n"
handshake_response << "Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=\n"
handshake_response << "Sec-WebSocket-Protocol: quote\n"
send_message(handshake_response)
end
def on_receive(data)
puts 'RECEIVED: ' + data
end
end
end
EventMachine.run do
print 'Starting WebSocket server...'
EventMachine.start_server '0.0.0.0', 4000, QuoteService::WebSocket
puts 'running'
end
The handshake headers are per Wikipedia.
I think that once the connection is open the request and response have already occurred, so sending headers at that point is too late. In addition, headers have to end with a blank line, which you omitted.
According to the demos, you don't even have to set headers in the client or the server--the ruby module automatically takes care of the headers on the server side, and html5 automatically takes care of the headers on the client side. I think this should work:
require "em-websocket-server"
class EchoServer < EM::WebSocket::Server
def on_connect
EM::WebSocket::Log.debug "Connected"
puts "I felt a connection."
end
def on_receive msg
puts "RECEIVED: #{msg}"
send_message msg
end
end
EM.run do
myhost = "0.0.0.0"
myport = 8000
puts "Starting WebSocket server. Listening on port #{myport}..."
EM.start_server myhost, myport, EchoServer
end
html file:
<!DOCTYPE html> <html> <head><title>Test</title>
<script type="text/javascript">
var myWebSocket = new WebSocket("ws://localhost:8000");
myWebSocket.onopen = function(evt) {
console.log("Connection open. Sending message...");
myWebSocket.send("Hello WebSockets!"); };
myWebSocket.onmessage = function(evt) {
console.log(evt.data);
myWebSocket.close(); };
myWebSocket.onclose = function(evt) {
console.log("Connection closed."); };
myWebSocket.onerror = function(err) {
alert(err.name + " => " + err.message); } </script>
</head> <body> <div>Hello</div> </body> </html>
And it does work in Safari 5.1.9 (which is an older browser): I see the expected output on both the server and the client. However, the code does not work in Firefox 21: I get the error message...
Firefox can't establish a connection to the server at ws://localhost:8000/.
var myWebSocket = new WebSocket("ws://localhost:8000");
I notice that in both Firebug and Safari Developer Tools, the server does not send a Sec-WebSocket-Accept header:
Response Headers
Connection Upgrade
Upgrade WebSocket
WebSocket-Location ws://localhost:8000/
WebSocket-Origin null
Request Headers
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Cache-Control no-cache
Connection keep-alive, Upgrade
DNT 1
Host localhost:8000
Origin null
Pragma no-cache
Sec-WebSocket-Key r9xT+ywe533EHF09wxelkg==
Sec-WebSocket-Version 13
Upgrade websocket
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:21.0) Gecko/20100101 Firefox/21.0
Nothing I tried would make the code work in Firefox 21.0. To check whether Firefox 21.0 even supports websockets, I went to:
http://www.websocket.org/echo.html
and it said my browser does support websockets.
Is there any reason you have to use the em-websocket-server module? The last modification for that module on github was three years ago. And whenever you see require rubygems in ruby code, that should alert you that the code is old. I tried the newer em-websocket module, and I was able to successfully transfer data back and forth using websockets on both Firefox 21.0 and Safari 5.1.9:
require 'em-websocket'
myhost = "0.0.0.0"
myport = 8000
EM.run {
puts "Listening on port #{myport}..."
EM::WebSocket.run(:host => myhost, :port => myport, :debug => false) do |ws|
ws.onopen do |handshake|
path = handshake.path
query_str = handshake.query
origin = handshake.origin
puts "WebSocket opened:"
puts "\t path \t\t -> #{path}"
puts "\t query_str \t -> #{query_str}"
puts "\t origin \t -> #{origin}"
end
ws.onmessage { |msg|
ws.send "Pong: #{msg}"
}
ws.onclose {
puts "WebSocket closed"
}
ws.onerror { |e|
puts "Error: #{e.message}"
}
end
}
Same client side code. Now the response headers include Sec-WebSocket-Accept:
Response Headers
Connection Upgrade
Sec-WebSocket-Accept LyIm6d+kAAqkcTR744tVK9HMepY=
Upgrade websocket
Request Headers
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Cache-Control no-cache
Connection keep-alive, Upgrade
DNT 1
Host localhost:8000
Origin null
Pragma no-cache
Sec-WebSocket-Key pbK8lFHQAF+arl9tFvHn/Q==
Sec-WebSocket-Version 13
Upgrade websocket
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:21.0) Gecko/20100101 Firefox/21.0
In your code, I don't think you are setting any headers. Instead, you are just sending messages back and forth that happen to contain characters that look like headers. Apparently, your browser requires the Sec-WebSocket-Accept header in the response before it will allow the connection, and when the em-websocket-server module fails to set that header in the response, your browser refuses the connection.
The relevant source code for em-websockets-server looks like this:
module EM
module WebSocket
module Protocol
module Version76
# generate protocol 76 compatible response headers
def response
response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
response << "Upgrade: WebSocket\r\n"
response << "Connection: Upgrade\r\n"
response << "Sec-WebSocket-Origin: #{origin}\r\n"
response << "Sec-WebSocket-Location: #{scheme}://#{host}#{path}\r\n"
if protocol
response << "Sec-WebSocket-Protocol: #{protocol}\r\n"
end
response << "\r\n"
response << Digest::MD5.digest(keyset)
response
end
As you can see, it doesn't set the Sec-WebSocket-Accept header. That code is in a module called Version76, and searching google for websockets version 76 yields an obsolete protocol(which contains an example of a request and response):
https://datatracker.ietf.org/doc/html/draft-hixie-thewebsocketprotocol-76
Here is the current websockets protocol(which also contains an example of a request and response):
https://www.rfc-editor.org/rfc/rfc6455
Conclusion: em-websockets-server is obsolete.
Related
I'm trying to automate actions I can take manually in an iPhone app using Ruby, but when I do, I get a 502 bad gateway error.
Using Charles Proxy I got the request the iPhone app is making:
POST /1.1/user/-/friends/invitations HTTP/1.1
Host: redacted.com
Accept-Locale: en_US
Accept: */*
Authorization: Bearer REDACTED
Content-Encoding: gzip
Accept-Encoding: br, gzip, deflate
Accept-Language: en_US
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 66
Connection: keep-alive
X-App-Version: 814
invitedUserId=REDACTED&source=PROFILE_INVITATION
I wrote the following code in Ruby to send this same request:
#header_post = {
"Host" => "redacted.com",
"Accept-Locale" => "en_US",
"Accept" => "*/*",
"Authorization" => "Bearer REDACTED",
"Content-Encoding" => "gzip",
"Accept-Encoding" => "br, gzip, deflate",
"Accept-Language" => "en_US",
"Content-Type" => "application/x-www-form-urlencoded; charset=UTF-8",
"Connection" => "keep-alive",
"X-App-Version" => "814"
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
path = '/1.1/user/-/friends/invitations'
data = "invitedUserId=REDACTED&source=PROFILE_INVITATION"
resp, data = http.post(path, data, #header_post)
Unfortunately I get a 502 Bad Gateway Error when running this code.
One thing I noticed which I think is key to the solution here is that, in the POST request the mobile app is making, the content length is 66. But the length of the string "invitedUserId=REDACTED&source=PROFILE_INVITATION" with un-redacted userId is only 46.
Am I missing another form variable with format "¶m=value" which has length 20? Or am I missing something else?
Thank you in advance!
This is probably not directly tied to the body length you're sending.
I see possibly 2 problems here :
the 502 error : are your uri.host and port correct ? A 502 error means there is something wrong on the server side. Also try by removing the Host header.
body content is not gzipped
You're defining an header Content-Encoding: gzip but you didn't compress the data (Net::Http doesn't do that automatically).
Try with something like that :
require "gzip"
#header_post = {
# ...
}
http = Net::HTTP.new(uri.host, uri.port)
path = '/1.1/user/-/friends/invitations'
data = "invitedUserId=REDACTED&source=PROFILE_INVITATION"
# instanciate a new gzip buffer
gzip = Zlib::GzipWriter.new(StringIO.new)
# append your data
gzip << data
# get the gzip body and use it in your request
body = gzip.close.string
resp, data = http.post(path, body, #header_post)
Alternatively, maybe the server is accepting a non-gzipped content. You could try simply by deleting the Content-Encoding
error from your original code.
However if it was the only mistake, the server should not send a 502 but a 4xx error. So I'm guessing there is another issue there with the uri config like a suggested above.
I want to use the Cloudera API from ruby. Therefore I want to
update the configuration of the cloudera manager, which is done
with an HTTP PUT request containing some json data.
The URL is http://localhost:7180/api/v11/cm/config, my first approach was the following code:
require 'net/http'
require 'base64'
port = 7180
host = 'localhost'
req = Net::HTTP::Put.new('/api/v11/cm/config')
req.body = '{"items":[{"name":"TSQUERY_STREAMS_LIMIT","value":1000},{"name":"parcel_proxy_server","value":"proxy"},{"name":"parcel_proxy_port","value":"8080"},{"name":"parcel_update_freq","value":"1"}]}'
req['Content-Type'] = 'application/json'
req['Authorization'] = "Basic #{Base64.encode64('admin:admin')}"
client = Net::HTTP.new(host, port)
resp = client.request(req)
puts resp
puts resp.to_hash
puts resp.body
This variant returns a 400 Bad Request response with the message
"message" : "No content to map due to end-of-input\n at [Source: org.apache.cxf.transport.http.AbstractHTTPDestination$1#4561cbc9; line: 1, column: 1]"
If I want to trace this in wireshark with the setting tcp.port == 7180, this request does somehow not show up.
Then I switched to using Net::HTTP.start with the following source code:
require 'net/http'
require 'base64'
port = 7180
host = 'localhost'
req = Net::HTTP::Put.new('/api/v11/cm/config')
req.body = '{"items":[{"name":"TSQUERY_STREAMS_LIMIT","value":1000},{"name":"parcel_proxy_server","value":"proxy"},{"name":"parcel_proxy_port","value":"8080"},{"name":"parcel_update_freq","value":"1"}]}'
req['Content-Type'] = 'application/json'
req['Authorization'] = "Basic #{Base64.encode64('admin:admin')}"
resp = Net::HTTP.start(host, port) { |client| client.request(req) }
puts resp
puts resp.to_hash
puts resp.body
This thing also returned a bad request, but without any body or else. In contrast to the first approach, this one showed up in wireshark but the body it posted was depicted as with Hypertext-Transfer-Protocol. If I do the same request with cURL, the request body is correctly shown as JavaScript Object Notation.
Does anybody know what the problem of my requests is?
Ok so I tried around for several hours but this behavior is rather strange:
To resolve this I first used Net::HTTP.start instead of Net::HTTP.new. Second and even more important:
I used to do basic authentication by setting the header value manually as you could see in
req['Authorization'] = "Basic #{Base64.encode64('admin:admin')}"
But Net::HTTP::Put already provides a basic_auth method. If this one is not used, strange errors as the ones that I got will occur.
So the working version looks like this:
req = Net::HTTP::Put.new('/api/v11/cm/config')
req.body = '{"items":[{"name":"TSQUERY_STREAMS_LIMIT","value":1000},{"name":"parcel_proxy_server","value":"proxy"},{"name":"parcel_proxy_port","value":"8080"},{"name":"parcel_update_freq","value":"1"}]}'
req['Content-Type'] = 'application/json'
req.basic_auth 'admin', 'admin'
resp = Net::HTTP.start(host, port) { |client| client.request(req) }
quite new to this been learning about API's in Ruby. Using an Emails service's API to create a user in a system.
This is an example of the POST:
POST http://localhost:8080/core/postgres-pages-xy/api/rest/v4/user/create?email=user003#test.invalid HTTP/1.1
Authorization: Basic bWFzdGVyQGVuubXVjLmVjaXJjbGUuZGU6aDhuc3d1cnN0
User-Agent: curl/7.29.0
Host: localhost:8080
Proxy-Connection: Keep-Alive
Content-Type:application/json
Accept:application/json
Content-Length: 86
[{"name":"user.FirstName","value":"Stan"}, {"name":"user.LastName", "value":"Laurel"}]
I think I am close(ish)? in Ruby was hoping someone would tell me how I send my authentication through. System requires login headers not sure how to do that, will be an email and a password:
require 'uri'
require 'net/http'
uri = URI("https://site.com/api/rest/v4/user/create?email=ruby1#ruby.com")
https = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.path)
request.basic_auth 'email', 'pass'
request["user.FirstName"] = 'Liam'
request["user.LastName"] = 'Coates'
response = https.request(request)
puts response
Thanks for feedback or learnings.
You can enter the credentials in the URL:
url = "http://username:password#localhost:8080/core/postgres-pages-xy/api/rest/v4/user/create"
If the username and password are there, it should automatically do HTTP basic auth (source).
However supposedly this is deprecated, so there is a longer solution:
req = Net::HTTP::Post.new(uri)
req.basic_auth 'user', 'pass'
res = Net::HTTP.start(uri.hostname, uri.port) {|http|
http.request(req)
}
puts res.body
I wrote a very simple HTTP server at my PC by ruby. I can access the it from IE/FF/Chrome and display the result XML correctly, however when I tried to access it by a Jquery AJAX call, looks like it only got the header of the response. Anyone can help?
the code of ruby
server = TCPServer.new('localhost', 9000)
loop {
client = server.accept()
while((x = client.gets) != "\r\n")
puts x
end
$result= "<root><data>123</data></root>"
headers = ["HTTP/1.1 200 OK",
"Date: Tue, 14 Dec 2010 10:48:45 GMT",
"Server: Ruby",
"Content-Type: text/xml;charset=gb2312",
"Content-Length: #{$result.bytesize}\r\n\r\n"].join("\r\n")
client.puts headers
client.puts $result
client.close
puts "Request Handled"
and the jquery code
$(document).ready(function(){
$("#btn").click(function(){
$.ajax({
url:"http://localhost:9000",
type:"GET",
dataType:"xml",
async:true,
timeout: 2000,
error: function(xml, status, err){
alert('Error loading XML document'+xml+status+err);
},
success: function(xml){
$(xml).find("data").each(function(i){
alert($(this).text());
});
}
});
});
});
ok, I got the reason. AJAX should only access the URL in the same domain. I put the AJAX code in a local HTML, hence can't access the server.
updated server code as below, and put the AJAX html under "/", the access point it "http:/ /localhost : 9000/ajax.html"
require 'webrick'
$mydata="<root><data>test1</data><data>test2</data></root>"
class MyServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(request, response)
response.status = 200
response.content_type = "text/xml"
response.body = $mydata
end
end
server = WEBrick::HTTPServer.new(:Port => 9000,:DocumentRoot=>Dir::pwd)
server.mount "/data", MyServlet
trap("INT"){ server.shutdown }
server.start
I've got a simple eventmachine web socket server (eventmachine 1.0.0):
EM.run {
# WebSocket Server
EM::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
ws.onopen do
sid = #channel.subscribe{|msg| ws.send msg }
puts "* new WebSocket client <#{sid}> connected!"
end
ws.onmessage do |msg|
puts "* websocket client <#{#sid}> : #{msg}"
end
ws.onclose do
#channel.unsubscribe(sid)
puts "* websocket client <#{#sid}> closed"
end
end
}
I'm trying to connect to it through a javascript client with the following code:
socket = new WebSocket("ws://localhost:8080");
socket.onopen = function(e) {
socket.send('Connesso');
};
socket.onmessage = function(mess) {
if (mess) {
socket.send(mess);
}
};
socket.onclose = function(e) {
socket.send('Disconnesso');
};
With previous versions of safari it was working flawlessly with the latest one the client is not connecting to the server.
I tried it also with last Chrome Dev stable version but it's not working.
The web socket header is sent but it remains in pending status.
If I send a text message to the web socket I receive the INVALID_STATE_ERR: DOM Exception 11.
I saw that there has been a draft change but I thought em-websocket 0.3.8 already implemented it.
Can you help me solve this issue?
Thanks a lot
INVALID_STATE_ERR: DOM Exception 11 means your websocket is not in ready state yet.
you can check state of websocket object by socket.readyState
you are able to send messages when socket.readyState == 1
I created a turnaround for this by using timeout
timerId = setInterval(sendDataWhenReady, 1000);
function sendDataWhenReady(){
if(socket.readyState == 1){
ws.send(JSON.stringify({"type": 'STATUS', "status": status, "username": logged_in_user}))
clearInterval(timerId);
}
}