AJAX Cross Domain Proxy

Under Work on August 02nd, 2007 with 50 comments

Update – May 2012 Code has been moved to Github as PHP Cross Domain Proxy

Update – Sep 2011 I have updated the AJAX Cross Domain Proxy script according to the latest comments and suggestions. Please note that by default the script accepts only full URLs. However, you can change this by switching CSAJAX_FILTER_DOMAIN to false. This will allow you to list domains (instead of long URLs) in $valid_requests. Also, during development you can take advantage of the CSAJAX_DEBUG which by default is set to false. Switch it to true to receive debugging messages; do not forget to switch it back to false on production.

It is well known that cross domain AJAX requests (XMLHTTPRequest) are not permitted due to security reasons. Numerous workarounds exist such as cross domain JSON and Flash but some of them are not suitable for every single case. For instance, cross domain JSON assumes that remote server is able not only to serve JSON but to include a call to the specified function (the callback function) as well. On the other hand, Flash method assumes that… well, that Flash is enabled!

An interesting approach is presented by Cameron Adams in his great article Go forth and API. Cameron suggests to take advantage of mod_rewrite or mod_proxy module in Apache in order to redirect our calls in external domains; a simple but ingenious solution! However, the most common solution is the application proxy which is accompanied by some advantages outlined perfectly well by Jonathan Snook:

[...] you have more control over the entire lifecycle. You can parse the data from the remote server, do with it what you will before sending it back to the client. If anything fails along the way, you can handle it in your own way. And lastly, you can log all remote calls. With that you can track success, failure and popularity.

Cross Domain Ajax: a Quick Summary

Lately, I have developed an application proxy in PHP which I decided to publish. You can have a look at the demo and of course download it.

How it works? All you have to do is to place the corresponding file in your web server. Whenever you want to make a cross domain request, just make a request to http://www.yourdomain.com/ajax-proxy.php and specify the cross domain URL in parameter csurl. Obviously, you can add more parameters according to your needs; note that the rest of the parameters will be used for the cross domain request. For example, if you are using jQuery:

$('#target').load(
	'http://www.yourdomain.com/ajax-proxy.php', {
		csurl: 'http://www.cross-domain.com/',
		param1: value1, param2: value2
	}
);

It’s worth mentioning that both POST and GET methods work, while headers were taken into consideration. That is to say, headers sent from browser to proxy are used for the cross domain request and vice versa. Finally, for security reasons you will need to define all the valid requests into the ajax-proxy.php file:

$valid_requests = array(
  'http://www.domainA.com/',
  'http://www.domainB.com/path-to-services/service-a'
);

Please note that the script is released under a CC-GNU GPL.

 


Comments (50)

Leave your comment ↓

  1. Howard Katz:

    I’ve been looking for something like this. I updated uploaded you $valid_requests array with the websites referenced in your demo site and uploaded the php code to my website, along with your html that references the php file.

    Unfortunately I couldn’t get it working. I know jquery is working, because once I click on one of the links, the #response div reports a “‘Loading! Please wait…” message that shortly disappears, to be replaced by … nothing!

    I’m new to php so I’m not sure what I could return (if anything) from ajax-proxy.php to help to debug the problem. Any suggestions? Since I’m a nubie, being as concrete as possible would be helpful!

    Thanks,
    Howard

  2. Iacovos: Author comment

    It seems that the ajax-proxy exits without making any request at all. What I forget to mention above is that you have to specify the exact URLs in $valid_requests array; not just the domains. For instance, if you are making requests to http://example.com/service, then neither http://www.example.com/service or http://example.com would work.

    Hope this solves your problem! If the problem persists you can always access directly the ajax-proxy via your browser i.e. http://www.yourdomain.com/ajax-proxy.php?csurl=http://www.iacons.net/feed/ and check the actual output.

    Thank you for stopping by and leaving your feedback!

  3. Richard:

    why would i get this error when i load the proxy via web browser? I am sure I properly initiated the csurl param

    Bad Request (Invalid Header Name)

  4. Richard:

    I removed this line if code and now it works.
    //curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);

    I will tell you I looked at the headers and it seems there were no header names and a bunch of 0′s for values.

  5. Iacovos: Author comment

    Thank you Richard for your feedback. The line of code that you have erased copies the headers sent by your browser into the request send by the ajax-proxy to your cross-domain application. Is it possible to let me know both your browser (name, version, os) and your cross-domain application in order to debug it?

  6. sanjay:

    i am not getting any response nor any error after adding the domains to the array list on php file.. Could you please help!!

  7. kevin:

    it’s a pity, though really useful, there is no demo proxy developed in asp , for which i am dying . still thanks a lot.

  8. fedmich:

    great…

    useful but the curl on my site isn’t enabled… so Im using other means of retrieving websites like fopen()

    for asp code, I had something like this before, just dont remember on which website did I used this for

    Cheers and Happy New Year

  9. Iacovos: Author comment

    fedmich, did you modify the ajax-proxy.php to work with fopen etc? If yes, it would be nice if you could provide me your changes, integrate them into the current script and finally release an updated version that works with both approaches!

    Happy New Year!

  10. Al:

    I get… Fatal error: Call to undefined function curl_init() in C:\root\www\crosssdomainajax\ajax-proxy.php on line 67

  11. Iacovos: Author comment

    Al, you need to enable curl module in PHP.

  12. ONi:

    Iacovos: Sir, you are a genious!! this is exactly what I was looking for and works like charm. I made a little modification though, to the issue in the comment #2, I added $_GET['csurl'] to the list of $valid_requests, so on, any URL that you try to get will be a valid request, hope this works for everyone!.

  13. Iacovos: Author comment

    ONi, thanks a lot. Regarding your little modification, that was not necessary. You could simply set the value of CSAJAX_FILTERS to false (line #13). However, you (not just you, but everyone) must understand the security issue behind the filtering option. By disabling the filtering option, the ajax-proxy script can serve as an open, proxy script and anyone could use it to request any page. I am not going further and analyze how someone could take advantage of it; if anyone has more questions, feel free to contact me.

  14. Silverlight Networking - Using a proxy to overcome cross-domain-scripting troubles » Mark Monster:

    [...] tested this proxy, written in PHP. It works for me because I’ve got a Webhost that supports PHP and no ASP.NET. I’ve [...]

  15. Lars:

    Hi Iacovos,

    Thanks for this handy example.
    I am using it in my prototype web app (a gmaps mashup).

    Re: 13, it seems like it would be useful for $valid_requests to be an array of domains, or hostnames, or URL prefixes, or match patterns, so that they’re more general than exact URLs.
    Is there much security risk in just specifying the hosts (or domains) that can be proxied to?

    Lars

  16. phil:

    There’s some bugs in your code…

    When posting it fails line 77 should be:
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($request_params));

    If csurl is giving as a GET variable, but the data is posted it fails at line 39. Changing it to:
    $request_url = urldecode($_REQUEST['csurl']);
    is the quickest way to fix it.

    Other than than nice work :)

  17. Joran:

    JSONP and Flash have their disadvantages. Flash also limits the request headers. But with the proxy approach the client has to make a request to the proxy which then has to make a request to the API, which then responds to the proxy which then responds to the client. In other words, the approach incurs double the latency and double the bandwidth on the system as a whole. In other words, it’s much slower than JSONP and Flash.

  18. Beautifying York Lists with AJAX — Breakz < drum & bass . breakz . hip-hop > Alternative music in York:

    [...] proxy, which takes a local request and redirects it to an external domain. The one I used is the PHP AJAX proxy by Iacovos [...]

  19. Ryan Underdown:

    Thanks Iacovos (and Phil, line 77 change got post working for me as well.) Was struggling with cross domain $.ajax for a day or so before I ran across this.

  20. a3cube:

    This is great but I think you should update the code to Phil’s suggestion on line 77 should be:
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($request_params));

    I ran into the same problem until I saw he’s comment which resolve everything

  21. Iacovos: Author comment

    Thank you guys for your comments and suggestions. I will get into the code and make the appropriate changes ASAP.

  22. a3cube:

    I ran into some problem when I was working with $_POST. Actually, I changed

    $request_params = $request_method == ‘GET’ ) ? $_GET : $_POST;

    to

    $request_params = $_REQUEST;

    but I noticed (via var_dump()) that something like action=true&register=1 will end you with action=true&register=1 which will make it hard for you to get $_REQUEST['register'].

    so I change

    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($request_params));

    to

    curl_setopt($ch, CURLOPT_POSTFIELDS, $request_params);

    I hope this would help someone.

  23. a3cube:

    [quote comment="129654"]but I noticed (via var_dump()) that something like action=true&register=1 will end you with action=true& amp;register=1 which will make it hard for you to get $_REQUEST['register']. [/quote]

  24. Iacovos: Author comment

    a3cube thank you for your valuable feedback.

    As far as I understood you are trying to perform a GET request to the AJAX Cross Domain Proxy which eventually will make a POST request? If this is the case, I am afraid this is beyond the functionality of this script. Try issuing a POST request to the proxy and this will transfer your post request to the final target.

    P.S. The script has been updated according to the latest comments.

  25. a3cube:

    No it’s a post request via jQuery

    $.ajax({
    type: “POST”,
    url: “ajaxproxy)?URL_AJAXPROXY:”).URL_REMOTE ; ?>”,
    data: unescape(“action=register_user&register=1″),
    success: function(response){
    $(“#reg_process”).hide();
    $(‘#rsp_display’).html(response);
    $(‘#rsp_display’).show();
    },
    error:function(xhr,err,e){
    alert( “Error: ” + err );
    }
    });

    My last comment dis not work on chrome and Opera. So I made a change to it by

    curl_setopt($ch, CURLOPT_POSTFIELDS, html_entity_decode(http_build_query($request_params)));

    this would convert & amp; to &. The reason for this is that http_build_query() encode html enities like & to & amp;

    I hope this help.

  26. Kyle Simpson:

    I have a project called flXHR http://flxhr.flensed.com which is a javascript+flash solution for cross-domain Ajax, but goes one step further: flXHR implements an identical API to the native XHR object, which means it can be dropped into any existing project with almost no code changes necessary, and you automagically will get cross-domain Ajax capability.

  27. Matias:

    Hi Iacovos,

    Great work!

    I downloaded the example and work seamless. However, I need to request the following URL and it doesn’t work:

    http://xmlfeed.laterooms.com/index.aspx?aid=1000&rtype=7&hids=73737&sDate=2010-03-28&nights=4

    I specified the whole domain and also the domain with page and parameters in ajax-proxy.php. None of the options seem valid

    $valid_requests = array(
    ‘http://xmlfeed.laterooms.com/’,
    ‘http://xmlfeed.laterooms.com/index.aspx?aid=1000&rtype=7&hids=73737&sDate=2010-03-28&nights=4′
    );

    Appreciate your help!

  28. Gustav:

    Hello Jacobus, please remove the counter of the download link, it doesn’t works.

    coz stats.iacons.net is down
    link directly at:

    http://lab.iacons.net/ajax-proxy/ajax-proxy.phps

    Thanks Jacobus, your code will save me hours and days of work.

    Gustav
    micro ISV

  29. ahmed:

    Please , I want to download the file but the link is currupted.

  30. Iacovos: Author comment

    Thank you ahmed, the link has been updated.

  31. rudraksha:

    I ran into the same problem until I saw phil’s comment which resolve everything

  32. Paul Albinson:

    Many thanks for the code it got me out of a awkward situtation where I needed to use a script from a different sub domain via AJAX.

    A few thoughts/fixes though:

    If you wish to make the system restrict to domains/sub domains rather than having to specify every single url you can change

    if ( !in_array($check_url, $valid_requests) ) {

    to

    if ( !in_array($parsed['host'], $valid_requests) ) {

    you can then use the valid_requests array like so:

    $valid_requests = array(
    ‘a.example.com’,
    ‘b.example.com’,
    ‘anotherdomain.com’
    );

    Also:

    I had the same problem as Richard mentioned in comment 3 and like him found that removing the following line solved it:

    curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);

    It seems that it doesn’t actually get all the headers we need and thus breaks it. Seems to work perfectly well without it. I see the point of using it to include the original headers but as it doesn’t actually work then it is surpless to requirment unless it is fixed.

    Also the comment made by a3cube (comment 22)

    “problem when I was working with $_POST. Actually, I changed

    $request_params = $request_method == ‘GET’ ) ? $_GET : $_POST;

    to

    $request_params = $_REQUEST;”

    was really useful as I had my jQuery AJAX using post but had the url I wanted in a get url. Alternatively I guess you could put the url in the post as well and thus can only deal with post requests.

  33. Paul Albinson:

    Another issue I found is that using the proxy can mess up any other ajax script you have on the page as in my situation it stopped the other scripts seeing the Zend Framework Authentication which was a major issue for the site as it stopped the other features working. The solution in my situtation was to remove the headers from the response, mad I agree but fixed my particular issue.

  34. Emilio Alvarez:

    Thanks for your work. We try your ajax cross domain proxy and works perfect.

    Thanks.

  35. Lugdum:

    I’m trying to use it to sent an xml request to a web service.
    The content type is of my request is text/xml but I have an error : Unsupported Content-Type: application/x-www-form-urlencoded Supported ones are: [text/xml]
    Any idea ?

  36. Brett:

    Great script to start from. I am now using a slightly modified version of the code for a site I am working on, and am very appreciative for your work.

    One thing I did run into was that if a response contained more than one cookie, only one of the cookies would be propagated back thru the proxy.

    I changed:
    header($response_header);
    to
    header($response_header, FALSE);
    which fixed the issue.

  37. Brett:

    Found what looks like another bug. When copying the request headers, I had to change another line of code.

    I changed:
    $request_headers[$headername] = $value;
    to
    $request_headers[] = “$headername: $value”;

  38. Brett:

    Due to the bug fix in #37, headers are now correctly being propagated. However, we don’t want to propagate the “Host” header, b/c the Host value of your proxy server is probably different than the Host value for the URL you are forwarding to. Thus, I simply changed the code to:

    if ($headername != ‘Host’) {
    $request_headers[] = “$headername: $value”;
    }

  39. EdisonCode:

    Thank you very much for this code. I ended up modifying it quite a bit to post json data to a .net web service and return the resulting payload to a jQuery application. My AJAX requests is similar to:

    $.ajax({
    type: “POST”

    data: ‘{ “json” : “data”, … }’;

    });

    The main change that I’d like to pass on for others trying to accomplish something similar with this excellent proxy code is to change:

    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($request_params));

    to

    curl_setopt($ch, CURLOPT_POSTFIELDS, $HTTP_RAW_POST_DATA);

    I also found it useful to completely wipe out the header data and send the only http header that I needed by doing:

    $headerData = array();
    array_push($headerData, ‘Content-Type: application/json;’);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);

    Hope this helps others and thanks again for a great starting place.

  40. Iacovos Constantinou: Author comment

    I have updated the AJAX Cross Domain Proxy script according to the latest comments and suggestions. Thank you all for your feedback.

  41. Hadrian:

    Hi!

    Good job!
    The change I’ve made for json retrieving it’s so simple like that : use the htmlspecialchars_decode function with the response_content.

    Somebody know a plugin for symfony to do that?

  42. Ajax call from external domain « Codeperl's Knowledge Sharing System:

    [...] http://iacons.net/2007/08/02/ajax-cross-domain-proxy/Like this:LikeBe the first to like this post.   [...]

  43. Andreas:

    Hello,
    I need to make a json (NOT jsonp) cross-domain POST.
    I tried different things… – but they fail all – probably because I’m a rookie ;-)
    Does anybody know how to achieve this using the ajax-proxy.php?

    Any help welcome!
    Thanks

  44. www.authorstream.com:

    Today, I went to the beach with my children. I found a sea shell and
    gave it to my 4 year old daughter and said “You can hear the ocean if you put this to your ear.” She put the shell to her ear and screamed.
    There was a hermit crab inside and it pinched her ear.

    She never wants to go back! LoL I know this is totally off topic
    but I had to tell someone!

  45. elektrownie atomowe:

    My spouse and I absolutely love your blog and find nearly all of your post’s
    to be exactly I’m looking for. Do you offer guest writers to write content to suit your needs?
    I wouldn’t mind writing a post or elaborating on most of the subjects you write in relation to here.
    Again, awesome site!

  46. Grosfillex Patio Furniture Usa:

    Today, outdoor furniture, the grosfillex resin chairs mission style etc.
    This will improve their life. These woods can withstand the
    weather is like there are a few people. If grosfillex resin chairs you feel
    like they are absolutely alluring. Cleaning resin outdoor furniture for
    export. So in essence it can easily cleaned. Wrought iron Toronto is the most durable woods.

  47. private registration for domain name .co.uk:

    Wow, this article stanjds out, my youngver ssister is evaluating it as part of her literature studiues and just though you
    may like to know.

  48. Latosha:

    A fascinating discussion is worth comment. I do think that you ought to publish more oon this topic, it
    might noot be a taboo subject but usually people
    don’t speak about such issues. To the next!
    Cheers!!

    Feel ffree to surrf to my blog post – webage (Latosha)

  49. Replacement Cushions For Resin Furniture:

    Teak furniture is chaise miami grosfillex just as well as furniture stores in January,
    can enhance its beauty and elegance to any space.
    Even so, there is growing in chaise miami grosfillex popularity.
    Then turn the cushions to dry it well. Most of the outdoor area is a must if you’ve an adjustable arm.
    Simply browse through the comments and testimonials of the type of wood cannot.

  50. Susanna:

    If you are looking for kitchen cookware to stay in your life for a
    while, invest in something a bit heavier such as copper
    or stainless cookware. It can make terrific and
    instant meals for breakfast, lunch and dinner, and is perfect for
    traveling, excursions, and parties. In these cases you would want cookware
    that would only be used for the microwave oven.


Leave your comment

Personal details

(required)

(will not be published, required)

Your comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>