I am a newbie to Bottle and sort of to Python as well and I am trying create an app whenever I click a button, an AJAX is fired and POST a json to server and store it using SQLite.
However, at current stage, I am trying to figure out how do I successfully received the data in the server.
On the client side,
I have the following send_data function written in JavaScript.
function send_data(feedback) {
$.ajax({
url: "/feedback",
type: "POST",
data: JSON.stringify(feedback),
contentType: "application/json",
success: function() {
alert("Feedback successfully stored in the server!");
},
error: function() {
alert("Feedback failed to store back in the server!");
},
}
The passed in parameter feedback looks something like {"id1": 1, "id2": 2}.
On the server side, I have a feedback.py file and the code is
from bottle import request, route, run
#route('/feedback', method='POST')
def feedback():
comments = request.json
print comments
run(host='localhost', port=8080)
Right now, I just want to check if I have received the data successful. But everytime, when I click that botton, I receive the following error
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/feedback. This can be fixed by moving the resource to the same domain or enabling CORS.
OPTIONS http://localhost:8080/feedback [HTTP/1.0 405 Method Not Allowed 2ms]
I am not sure if it's because I am not using the element <form>. That botton is technically just an image. Every time I click that image, send_data() function is fired.
Anyone can help? I really appreciate it. Thank you!
Cross Origins Requests are restricted as a security measure by the browser. It is possible to overcome that by setting an Access-Control-Allow-Origin header. You can can use bottle-cors or create a decorator like the following on your server code (Python/bottle):
def enable_cors(fn):
def _enable_cors(*args, **kwargs):
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
if request.method != 'OPTIONS':
# actual request; reply with the actual response
return fn(*args, **kwargs)
return _enable_cors
and then with your example:
from bottle import request, route, run
#enable_cors
#route('/feedback', method='POST')
def feedback():
comments = request.json
print comments
run(host='localhost', port=8080)
Note that it's best to allow specific origins instead of using *.
You can read more about Cross-Origin Resource Sharing (CORS) here
You can make this work by disabling the Cross-origin restrictions.
On safari, open the Develop panel, and check disable cross-origin restrictions, or use a browser like Firefox.
Related
Just started learning HTTP, trying to make a simple client-to-server request to work in tornado. Here is my javascript function trigged by a button click:
function funcOnClick(){
$.ajax({
url: "http://localhost:8889/madTest",
type: "GET",
success: function(result){alert('success')},
error: function(error){console.log(error)}
})
}
And here is my server:
import tornado.ioloop
import tornado.web
import json
class MainHandler(tornado.web.RequestHandler):
def get(self):
print('got the GET request')
self.write('abc')
def make_app():
return tornado.web.Application([
(r"/madTest", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8889)
print('server started, listening to 8889 ...')
tornado.ioloop.IOLoop.current().start()
On clicking the button, I see "got the GET request" printed, so the server must have gotten the request. However, I got this error on console:
Any suggestions? Thanks!
The error message occurs when your JS or HTML page is served from a different domain and you're trying to make an AJAX request to a different domain. This is called CORS. For security reasons, web browsers restrict cross-domain AJAX requests unless allowed by the server.
To be able to make cross origin AJAX requests, you'll need to set a header called Access-Control-Allow-Origin in your handler's response.
You can do this in the prepare method of your handler:
class MainHandler(...):
def prepare(self):
self.set_header('Access-Control-Allow-Origin', '*')
# the wildcard - '*' - allows CORS from any domain
# you should probably change it to your frontend
# domain name if you wan to restrict CORS to a single domain.
# see Mozilla docs for more info
I am working on an internal web application at work. In IE10 the requests work fine, but in Chrome all the AJAX requests (which there are many) are sent using OPTIONS instead of whatever defined method I give it. Technically my requests are "cross domain." The site is served on localhost:6120 and the service I'm making AJAX requests to is on 57124. This closed jquery bug defines the issue, but not a real fix.
What can I do to use the proper http method in ajax requests?
Edit:
This is in the document load of every page:
jQuery.support.cors = true;
And every AJAX is built similarly:
var url = 'http://localhost:57124/My/Rest/Call';
$.ajax({
url: url,
dataType: "json",
data: json,
async: true,
cache: false,
timeout: 30000,
headers: { "x-li-format": "json", "X-UserName": userName },
success: function (data) {
// my success stuff
},
error: function (request, status, error) {
// my error stuff
},
type: "POST"
});
Chrome is preflighting the request to look for CORS headers. If the request is acceptable, it will then send the real request. If you're doing this cross-domain, you will simply have to deal with it or else find a way to make the request non-cross-domain. This is why the jQuery bug was closed as won't-fix. This is by design.
Unlike simple requests (discussed above), "preflighted" requests first
send an HTTP request by the OPTIONS method to the resource on the
other domain, in order to determine whether the actual request is safe
to send. Cross-site requests are preflighted like this since they may
have implications to user data. In particular, a request is
preflighted if:
It uses methods other than GET, HEAD or POST. Also, if POST is used to send request data with a Content-Type other than
application/x-www-form-urlencoded, multipart/form-data, or text/plain,
e.g. if the POST request sends an XML payload to the server using
application/xml or text/xml, then the request is preflighted.
It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)
Based on the fact that the request isn't sent on the default port 80/443 this Ajax call is automatically considered a cross-origin resource (CORS) request, which in other words means that the request automatically issues an OPTIONS request which checks for CORS headers on the server's/servlet's side.
This happens even if you set
crossOrigin: false;
or even if you ommit it.
The reason is simply that localhost != localhost:57124. Try sending it only to localhost without the port - it will fail, because the requested target won't be reachable, however notice that if the domain names are equal the request is sent without the OPTIONS request before POST.
I agree with Kevin B, the bug report says it all. It sounds like you are trying to make cross-domain ajax calls. If you're not familiar with the same origin policy you can start here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Same_origin_policy_for_JavaScript.
If this is not intended to be a cross-domain ajax call, try making your target url relative and see if the problem goes away. If you're really desperate look into the JSONP, but beware, mayhem lurks. There really isn't much more we can do to help you.
If it is possible pass the params through regular GET/POST with a different name and let your server side code handles it.
I had a similar issue with my own proxy to bypass CORS and I got the same error of POST->OPTION in Chrome. It was the Authorization header in my case ("x-li-format" and "X-UserName" here in your case.) I ended up passing it in a dummy format (e.g. AuthorizatinJack in GET) and I changed the code for my proxy to turn that into a header when making the call to the destination. Here it is in PHP:
if (isset($_GET['AuthorizationJack'])) {
$request_headers[] = "Authorization: Basic ".$_GET['AuthorizationJack'];
}
In my case I'm calling an API hosted by AWS (API Gateway). The error happened when I tried to call the API from a domain other than the API own domain. Since I'm the API owner I enabled CORS for the test environment, as described in the Amazon Documentation.
In production this error will not happen, since the request and the api will be in the same domain.
I hope it helps!
As answered by #Dark Falcon, I simply dealt with it.
In my case, I am using node.js server, and creating a session if it does not exist. Since the OPTIONS method does not have the session details in it, it ended up creating a new session for every POST method request.
So in my app routine to create-session-if-not-exist, I just added a check to see if method is OPTIONS, and if so, just skip session creating part:
app.use(function(req, res, next) {
if (req.method !== "OPTIONS") {
if (req.session && req.session.id) {
// Session exists
next();
}else{
// Create session
next();
}
} else {
// If request method is OPTIONS, just skip this part and move to the next method.
next();
}
}
"preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
Consider using axios
axios.get( url,
{ headers: {"Content-Type": "application/json"} } ).then( res => {
if(res.data.error) {
} else {
doAnything( res.data )
}
}).catch(function (error) {
doAnythingError(error)
});
I had this issue using fetch and axios worked perfectly.
I've encountered a very similar issue. I spent almost half a day to understand why everything works correctly in Firefox and fails in Chrome. In my case it was because of duplicated (or maybe mistyped) fields in my request header.
Use fetch instead of XHR,then the request will not be prelighted even it's cross-domained.
$.ajax({
url: '###',
contentType: 'text/plain; charset=utf-8',
async: false,
xhrFields: {
withCredentials: true,
crossDomain: true,
Authorization: "Bearer ...."
},
method: 'POST',
data: JSON.stringify( request ),
success: function (data) {
console.log(data);
}
});
the contentType: 'text/plain; charset=utf-8', or just contentType: 'text/plain', works for me!
regards!!
Using angular v1.3.1 i got a singular the following problem trying to implement a facade for making http request to a REST + JSON interface in the backend of the web app.
I got something like this in the code:
findSomething(value: number): ng.IPromise<api.DrugIndication[]> {
const getParams = { 'param' : 'value' };
const config:ng.IRequestShortcutConfig = {
headers: {
"Content-Type" : "application/json"
},
data: getParams
}
return this.$http.get(url,config);
}
And when the times comes to invoke it, i got an 400 Bad Request (btw: Great name for a band!) because the backend (made with Play for Scala) rejects the request inmediately. So making an inspection in the request i see that no data is being send in the body of the request/message.
So how i can send some data in the body of and HTTP Get request using angular "$http.get"?
Additional info: This doesn't happen if i the make request using the curl command from an ubuntu shell. So probably is an problem between Chrome and angular.js
If you inspect the network tab in chrome development tools you will see that this is a pre-flight OPTIONS request (Cross-Origin Resource Sharing (CORS)).
You have two ways to solve this.
Client side (this requires that your server does not require the application/json value)
GET, POST, HEAD methods only
Only browser set headers plus these
Content-Type only with:
application/x-www-form-urlencoded
multipart/form-data
text/plain
Server side
Set something like this as a middleware on your server framework:
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization")
w.Header().Set("Access-Control-Max-Age", "86400") // firefox: max 24h, chrome 10min
return
}
For your specific framework this should work
Using config.data will send the data in the request body, use
config.params = getParams
This is from the documentation :
params – {Object.} – Map of strings or objects which will be serialized with the paramSerializer and appended as GET parameters
I have a web app using Python Django and Dojo framework.
I wanna send a PUT request from Dojo (using dojo/request) to server Django but when server receives a request, the data within are empty and validate Invalid.
BUT when I change method from PUT to POST, it's work correctly.
Here is my code:
_save: function(data){
var idForm = "editForm" + this.id;
var value = dijit.byId(idForm).get('value');
console.log(value);
request.put("/api/guestbook/"+this.bookName+"/greeting/"+this.id+"/", {
data: {
book_name: this.bookName,
message: value.message
},
headers: { "X-CSRFToken": _cookie('csrftoken') }
}).then(lang.hitch(this, function(text){
}));
},
And in Django:
def put(self, request, *args, **kwargs):
form = self.get_form(self.form_class)
logging.warning(form)
logging.warning(request.PUT)
if form.is_valid():
logging.warning("This form is VALID")
else:
logging.warning("This form is INVALID!!!")
Anyone can help me?
Thanks for help!
I found the way to receive PUT method below:
def put(self, request, *args, **kwargs):
request.PUT = QueryDict(request.body)
form = self.form_class(request.PUT)
if form.is_valid():
logging.warning("This form is VALID")
else:
logging.warning("This form is INVALID")
This is ok :)
Thanks all!
I'm guessing from your X-CSRFToken header that you are doing cross domain requests, i.e. CORS.
If you look in your browser's console, you'll probably see an OPTIONS request being sent to the server. This is called a "preflight request", and your server needs to respond with CORS headers telling the browser that it's okay to make the cross domain PUT request.
In your case, you want the server to respond with headers similar to:
Access-Control-Allow-Origin: http://your-site-hostname-and-port
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-CSRFToken
Simple POST requests do not need the preflight OPTIONS request, that's probably why it works. A pretty good tutorial on html5rocks here.
I have an API hosted on one domain that has CORS enabled with the following headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Max-Age: 1728000
I am able to make a GET or POST request from hackst.com and it works fine. Link: http://hackst.com/#w3SbV
From my backbone app hosted on another domain, GET requests work fine. But when I try to create and save a new model (i.e. make a POST request), it fails with the following error:
Failed to load resource: the server responded with a status of 501 (Not Implemented) http://projectwhatup.us:5000/api/posts
XMLHttpRequest cannot load http://projectwhatup.us:5000/api/posts. Origin http://ayush.projectwhatup.us is not allowed by Access-Control-Allow-Origin.
My relevant backbone code:
var newPostData = {
topic : "New Post",
body : "new body",
user_id : 1,
};
var newPostModel = new Post(newPostData);
this.model.create(newPostModel);
I even tried over-riding the create method and making a POST request manually like this:
create : function(data) {
console.log('overriden create');
$.ajax({
"url" : this.url,
"async" : true,
"beforeSend" : function(obj){
console.log(obj);
},
"contentType" : 'application/json',
//"crossDomain" : true, // uncommenting this doesnt help either
"headers" : {
},
"dataType" : 'json',
"type" : 'POST',
"data" : JSON.stringify(data),
"error" : function(err){
console.log('new post creation failed');
console.log(err);
},
"success" : function(resp){
console.log('new post created');
console.log(resp);
}
});
}
Same error.
I tried a stand-alone GET request on JSFiddle as well (http://jsfiddle.net/X9cqh/5/), but that fails even though my backbone app can make the GET request fine.
I'm completely clueless at this point. Any hints, pointers, solutions?
The server should also reply to the preflight with the following header:
Access-Control-Allow-Headers: Content-Type
This is necessary because the content type is application/json, which is outside the acceptable values defined in the CORS spec (http://www.w3.org/TR/cors/).
Your sever setup works. JSFiddle apparently does not make the ajax requests, but you can quickly test that it works by entering these four lines into Chrome console or Safari developer console:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://projectwhatup.us:5000/api/posts', false);
xhr.send();
xhr.responseText;
If you try this with a domain that does not allow CORS, it will error out.
The reason that adding a 'Content-Type' header makes your CORS request fail is because your server is set up wrongly.
If the client wishes to specify particular headers or use an unusual http method verb (e.g. PUT), then the browser will first do a 'preflight' OPTIONS call to ensure that it is allowed to set those headers. Your server needs to respond to this OPTIONS call with the appropriate headers. You'll see that options call in the network tab of the Chrome developer tools or firebug if you want to confirm that this is what the problem is.
You may be interested in my more detailed answer here.