Using Ajax to access Ruby HTTP server - ruby

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

Related

Ajax PATCH/PUT problem 401 (Unauthorized)

I wrote an API using django and djano-ninja.
Here is my section of api.py file which is imported to URL.
class ORJSONRenderer(BaseRenderer):
media_type = "application/json"
def render(self, request, data, *, response_status):
return orjson.dumps(data)
class ApiKey(APIKeyQuery):
param_name = "api_key"
def authenticate(self, request, key):
try:
return CustomUser.objects.get(api_key=key)
except CustomUser.DoesNotExist:
pass
api_key = ApiKey()
api = NinjaAPI(
title="Good TExt",
version="0.0.1",
description="That This",
renderer=ORJSONRenderer(),
# csrf=True
)
#api.patch(
"/car/color/{new_color}", auth=api_key, tags=["Car"], summary="Does something",
description="Does something"
)
def update_team_name(request, new_color):
try:
#Do something
msg = {"success": "Done"}
except:
msg = {"error": "Problem"}
return HttpResponse(json.dumps(msg), content_type='application/json')
I have other get endpoints too. There is no problem when I request get endpoints.
But when I send a request to patch endpoints I am getting 401 (Unauthorized) only with ajax. I mean python's requests work.
import requests
load = dict(
api_key='SOME HEY'
)
r = requests.get("http://127.0.0.1:8000/api/car/color/red", params=load)
print(r.text)
But javascript doesn't:
$.ajax({
url: "/api/car/color/red",
data: {
"api_key": "some key"
},
cache: false,
type: "PATCH",
success: function(response_country) {
console.log(response_country);
},
error: function(xhr) {
console.log(xhr);
}
});
What I did try
I tried to add:
headers:{"X-CSRFToken": $crf_token},
to header of the ajax request. Even though csrf is set to False in django-ninja
I tried to change from PATCH to PUT
I tried to add a timeout to ajax request
I tried to send the api_key trough header and not the data
with no success.

Ruby POST multipart/form-data results in 302 Found

I am trying to upload a csv file to my endpoint. I tried doing this and resulting in 302 Found.
Tried other solution mentioned here: Multipart POST Ruby HTTPS but same results.
First option using net/http
url = URI('https://abcd.com/test/upload')
out_file = '/Users/username/Downloads/test.csv'
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Post.new(url)
request["username"] = 'abcd#test.com'
request["password"] = 'test2345'
form_data = [['file', File.open(out_file)]]
request.content_type = 'multipart/form-data'
request.set_form form_data, 'multipart/form-data'
response = https.request(request)
puts "Response from CRA is #{response.read_body}"
result:
#<Net::HTTPFound 302 Found readbody=true>
Second option using 'rest-client' with the steps mentioned in https://github.com/rest-client/rest-client
out_file = '/Users/username/Downloads/test.csv'
url = "https://abcd.com/test/upload"
begin
response = RestClient.post(url,:file => File.new(out_file, 'rb'),:headers => { "username": "abcd#test.com", "password": "test2345"})
rescue RestClient::ExceptionWithResponse => err
end
result:
[319] pry(main)> err
=> #<RestClient::Found: 302 Found>
[320] pry(main)> err.response
=> <RestClient::Response 302 "<html><head...">
[321] pry(main)> err.response.follow_redirection
IOError: closed stream
[322] pry(main)>
response body:
"<html><head><title>Object moved</title></head><body>\r\n<h2>Object moved to here.</h2>\r\n</body></html>\r\n",
url=#<URI::HTTPS https://abcd.com/test/upload>
Can anyone help me understand what am I doing wrong?

nitrous.io websockets - Connection and Upgrade headers required [duplicate]

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.

Api Requests with Ruby gem Typhoeus

What is wrong with the following request?
request = Typhoeus::Request.new("http://fluidsurveys.com/api/v2/groups",
method: :get,
userpwd: "test_user:test_password",
headers: { 'ContentType' => "application/json"})
response = request.body
puts response
This returns undefined method body for #<Typhoeus::Request:0x007f8e50d3b1d0> (NoMethodError)
The following request works fine with httparty:
call= "/api/v2/groups/"
auth = {:username => "test_user", :password => "test_password"}
url = HTTParty.get("http://fluidsurveys.com/api/v2/groups",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/json' } )
response = url.body
puts response
EDIT:
I tried this:
response = request.response
puts response.body
with no luck. I receive this : undefined method body for nil:NilClass (NoMethodError)
From https://github.com/typhoeus/typhoeus
You need to do the get before the response body is available.
EDIT: Here is an operable solution. It doesn't use your website, which I couldn't access even manually. But, this returns response code 200 and the response_body. Running this in my debugger showed the complete response, which you could see using "puts response.inspect".
class TyphoeusTry
require 'typhoeus'
request = Typhoeus::Request.new("http://www.google.com",
method: :get,
userpwd: "test_user:test_password",
headers: { ContentType: "application/json"})
response = request.run
puts response.response_body
end
The problem is that you didn't actually execute your request. The following code should work.
request = Typhoeus::Request.new("http://fluidsurveys.com/api/v2/groups",
method: :get,
userpwd: "test_user:test_password",
headers: { 'ContentType' => "application/json"})
request.run
response = request.response
response_code = response.code
response_body = response.body

Rails Ajax -> Sinatra -> Amazon API and back

I'm not sure that I really understand how Sinatra works.
I'd like to get some products from Amazon using their API, in my Rails app. But HTTP requests are blocking the IO. I got the tip to create a Sinatra app and make an Ajax request to there instead.
Ajax: (From my Rails app)
$.ajax({
url: "http://sinatra.mydomain.com",
dataType: "json",
success: function(data) {
console.log(data);
}
});
Sinatra app: (I also make use of the Sinatra-synchrony gem)
require 'sinatra'
require 'sinatra/synchrony'
require 'erb'
require 'rest-client'
require 'amazon_product'
Sinatra::Synchrony.overload_tcpsocket!
get '/' do
req = AmazonProduct["us"]
req.configure do |c|
c.key = "KEY"
c.secret = "SECRET"
c.tag = "TAG"
end
req << { :operation => 'ItemSearch',
:search_index => "DVD",
:response_group => %w{ItemAttributes Images},
:keywords => "nikita",
:sort => "" }
resp = req.get
#item = resp.find('Item').shuffle.first
erb :layout, :locals => { :amazon_product => #item }
end
Layout.erb: (renders fine if I go to this Url in the browser)
<%= amazon_product %>
Problem:
My Ajax response is a 200 OK but with an empty response.
I'm can't figure out what's wrong. Please advise.
It seems that you've faced with ajax 'cross-domain security' problem. Try to use JSONP (JSON with padding).
Change your sinatra get handler:
get '/' do
req = AmazonProduct["us"]
req.configure do |c|
c.key = KEY
c.secret = SECRET
c.tag = TAG
end
req << { :operation => 'ItemSearch',
:search_index => "DVD",
:response_group => %w{ItemAttributes Images},
:keywords => "nikita",
:sort => "" }
resp = req.get
#item = resp.find('Item').shuffle.first
content_type :json
callback = params.delete('callback') # jsonp
json = #item.to_json
if callback
content_type :js
response = "#{callback}(#{json})"
else
content_type :json
response = json
end
response
end
And change your Ajax request:
$.getJSON("http://address_of_sinatra?callback=?",
function(data) {
console.log(data);
});
Or you can add dataType: 'jsonp' to your $.ajax request.
After that you should see data object in js debugger (at least it's working in my case :D )

Resources