使用PUT / POST / DELETE与JSONP和jQuery
我正在创build一个支持跨域请求,JSON / JSONP支持和主HTTP方法(PUT / GET / POST / DELETE)的RESTful API。 现在,虽然通过服务器端代码访问这个API很容易,但是将它暴露给javascript是很好的。 从我所知道的,当用jQuery做JSONP请求时,它只支持GET方法。 有没有办法使用POST / PUT / DELETE做一个JSONP请求?
理想情况下,我想从jQuery内部做到这一点(通过一个插件,如果核心不支持这一点),但我也会采取一个简单的JavaScript解决scheme。 任何链接到工作代码或如何编码将是有益的,谢谢。
实际上 – 有一种方法可以支持POST请求。 在PROXI服务器中不需要 – 只是一个小的实用程序HTML页面,如下所述。
以下是您如何有效地进行POST跨域调用,包括附加文件和多部分以及所有:)
首先是理解这个想法的步骤,然后find一个实现示例。
jQuery的JSONP如何实现,为什么不支持POST请求?
传统的JSONP是通过创build一个脚本元素并将其附加到DOM中实现的 – 这导致浏览器触发HTTP请求来检索标记的源代码,然后将其作为JavaScript执行,浏览器触发的HTTP请求很简单的GET。
什么不限于GET请求?
表单。 在指定action
跨域服务器的同时提交FORM。 FORM标记可以完全使用脚本创build,使用脚本填充所有字段,设置所有必要的属性,注入到DOM中,然后提交 – 全部使用脚本。
但是,我们如何提交一个FORM,而不刷新页面?
我们将target
窗体指定到同一页面的IFRAME中。 IFRAME也可以使用脚本创build,设置,命名和注入DOM。
但是,我们怎样才能从用户那里隐藏这项工作呢? 我们将在隐藏的DIV中使用style="display:none"
来包含FORM和IFRAME
(这里是最复杂的技术部分,耐心)
但是来自其他域的IFRAME无法在其顶级文档上调用callback。 如何克服呢?
事实上,如果FORM提交的响应是来自另一个域的页面,则顶级页面和IFRAME中的页面之间的任何脚本通信都会导致“访问被拒绝”。 所以服务器不能使用脚本callback。 服务器可以做什么? redirect 。 服务器可以redirect到任何页面 – 包括与顶层文档在同一个域中的页面 – 可以为我们调用callback的页面。
服务器如何redirect?
两种方式:
- 使用客户端脚本,如
<Script>location.href = 'some-url'</script>
- 使用HTTP头。 请参阅: http : //www.webconfs.com/how-to-redirect-a-webpage.php
所以我最后还有一个页面? 它对我有什么帮助?
这是一个简单的实用程序页面,将在所有跨域调用中使用。 实际上,这个页面实际上是一种proxi,但它不是一个服务器,而是一个简单而静态的HTML页面 ,任何有记事本和浏览器的人都可以使用。
所有这个页面所要做的就是调用顶层文档的callback函数,以及来自服务器的响应数据。 客户端脚本可以访问所有的URL部分,服务器可以将它的响应放在编码的一部分,以及必须调用的callback名称。 手段 – 这个页面可以是一个静态和HTML页面,并不一定是一个dynamic的服务器端页面:)
这个实用程序页面将从它所运行的URL中获取信息,具体来说就是在下面的实现中,Query-String参数(或者你可以使用anchor-ID编写你自己的实现 – 也就是说url的一部分到“#”标志)。 而且由于这个页面是静态的 – 甚至可以被caching:)
不会为每个POST请求添加一个DIV,一个SCRIPT和一个IFRAME最终会泄漏内存?
如果你把它留在页面中 – 它会的。 如果你清理后,它不会。 我们所要做的就是给DIV一个ID,每当响应从服务器到达时,我们就可以使用它来放置DIV和FORM和IFRAME。
我们得到什么?
有效地进行POST跨域调用,包括附加文件和多部分以及全部:)
有什么限制?
- 服务器响应仅限于redirect。
- 服务器必须总是返回一个REDIRECT POST请求。 这包括404和500错误。 或者,在发起请求之前在客户端上创build一个超时,以便您有机会检测到未返回的请求。
- 不是每个人都能理解这一切和涉及的所有阶段。 这是一种基础设施级别的工作,但是一旦你得到它运行 – 它岩石:)
我可以将它用于PUT和DELETE调用吗?
FORM标签不是PUT和DELETE。 但是,那更好,然后没有:)
好的,有了这个概念。 它是如何在技术上完成的?
我所做的是:
我创build了DIV,将其设置为不可见,并将其附加到DOM。 我也给它一个ID,我可以在服务器响应到达之后从DOM清理它(与JQuery清除JSONP SCRIPT的方式相同 – 但是DIV)。
然后,我编写一个包含IFRAME和FORM的string – 包含所有属性,属性和input字段,并将其注入到不可见的DIV中。 只有在div处于DOM之后,才能将该string注入到DIV中。 如果不是 – 它不适用于所有的浏览器。
之后 – 我获得了表格的参考并提交。 只记得之前的一行 – 设置一个超时callback ,以防服务器不响应,或者以错误的方式回应。
callback函数包含清理代码。 在响应超时的情况下,它也被定时器调用(并在服务器响应到达时清除它的超时定时器)。
给我看代码!
下面的代码片段在“纯粹的”javascript上完全是“中立的”,并声明了它所需要的任何实用工具。 只是为了简化解释的想法 – 这一切都运行在全球范围内,但它应该是一个更复杂一点…
按照您的需要组织function,并参数化您所需要的function – 但要确保所有需要彼此看到的部件在相同的范围内运行:)
对于这个例子 – 假设客户端在http://samedomain.com上运行,服务器在http://crossdomain.com上运行。;
顶层文档上的脚本代码
//declare the Async-call callback function on the global scope function myAsyncJSONPCallback(data){ //clean up var e = document.getElementById(id); if (e) e.parentNode.removeChild(e); clearTimeout(timeout); if (data && data.error){ //handle errors & TIMEOUTS //... return; } //use data //... } var serverUrl = "http://crossdomain.com/server/page" , params = { param1 : "value of param 1" //I assume this value to be passed , param2 : "value of param 2" //here I just declare it... , callback: "myAsyncJSONPCallback" } , clientUtilityUrl = "http://samedomain.com/utils/postResponse.html" , id = "some-unique-id"// unique Request ID. You can generate it your own way , div = document.createElement("DIV") //this is where the actual work start! , HTML = [ "<IFRAME name='ifr_",id,"'></IFRAME>" , "<form target='ifr_",id,"' method='POST' action='",serverUrl , "' id='frm_",id,"' enctype='multipart/form-data'>" ] , each, pval, timeout; //augment utility func to make the array a "StringBuffer" - see usage bellow HTML.add = function(){ for (var i =0; i < arguments.length; i++) this[this.length] = arguments[i]; } //add rurl to the params object - part of infrastructure work params.rurl = clientUtilityUrl //ABSOLUTE URL to the utility page must be on //the SAME DOMAIN as page that makes the request //add all params to composed string of FORM and IFRAME inside the FORM tag for(each in params){ pval = params[each].toString().replace(/\"/g,""");//assure: that " mark will not break HTML.add("<input name='",each,"' value='",pval,"'/>"); // the composed string } //close FORM tag in composed string and put all parts together HTML.add("</form>"); HTML = HTML.join(""); //Now the composed HTML string ready :) //prepare the DIV div.id = id; // this ID is used to clean-up once the response has come, or timeout is detected div.style.display = "none"; //assure the DIV will not influence UI //TRICKY: append the DIV to the DOM and *ONLY THEN* inject the HTML in it // for some reason it works in all browsers only this way. Injecting the DIV as part // of a composed string did not always work for me document.body.appendChild(div); div.innerHTML = HTML; //TRICKY: note that myAsyncJSONPCallback must see the 'timeout' variable timeout = setTimeout("myAsyncJSONPCallback({error:'TIMEOUT'})",4000); document.getElementById("frm_"+id+).submit();
跨域的服务器来自服务器的响应预计是一个REDIRECTION,可以通过HTTP头或编写一个SCRIPT标签。 (redirect更好,SCRIPT标签更容易用JS断点debugging)。 这里是头的例子,假设上面的rurl
值
Location: http://samedomain.com/HTML/page?callback=myAsyncJSONPCallback&data=whatever_the_server_has_to_return
注意
-
data
参数的值可以是JavaScript Object-Literal或JSONexpression式,但最好是使用url编码。 - 服务器响应的长度限于浏览器可以处理的URL的长度。
另外 – 在我的系统中,服务器有rurl
的默认值,所以这个参数是可选的。 但是,只有在客户端应用程序和服务器应用程序耦合的情况下才可以这样做。
发送redirect标头的API:
http://www.webconfs.com/how-to-redirect-a-webpage.php
或者,您可以让服务器写入以下响应:
<script> location.href="http://samedomain.com/HTML/page?callback=myAsyncJSONPCallback&data=whatever_the_server_has_to_return" </script>
但HTTP头将被视为更清洁;)
与顶层文档位于同一个域的实用程序页面
我使用与rurl
相同的实用程序页面rurl
处理所有发布的请求:它只是使用客户端代码将查询string中的callback名称和参数取出,并在父文档中调用它。 只有当这个页面在和请求的页面完全相同的域中运行的时候,它才能做到! 重要提示:与Cookie不同,子域名不会计数! 它必须是完全相同的域名。
如果这个实用程序页面不包含对其他资源(包括JS库)的引用,它也会使其效率更高。 所以这个页面是普通的JavaScript。 但是你可以实现它,只要你喜欢。
以下是我使用的响应者页面,其URL是在POST请求的rurl
中find的(例如: http : rurl
)
<html><head> <script type="text/javascript"> //parse and organize all QS parameters in a more comfortable way var params = {}; if (location.search.length > 1) { var i, arr = location.search.substr(1).split("&"); for (i = 0; i < arr.length; i++) { arr[i] = arr[i].split("="); params[arr[i][0]] = unescape(arr[i][1]); } } //support server answer as JavaScript Object-Literals or JSON: // evaluate the data expression try { eval("params.data = " + params.data); } catch (e) { params.data = {error: "server response failed with evaluation error: " + e.message ,data : params.data } } //invoke the callback on the parent try{ window.parent[ params.callback ](params.data || "no-data-returned"); }catch(e){ //if something went wrong - at least let's learn about it in the // console (in addition to the timeout) throw "Problem in passing POST response to host page: \n\n" + e.message; } </script> </head><body></body></html>
它不像jQuery那样有太多的自动化和“现成”库,并且需要一些“手动”的工作,但它有魅力:)
如果您对现成的图书馆非常感兴趣,那么您也可以查看一下Dojo Toolkit ,在最后一次检查时(大约一年前),对于同一个机制有自己的实现。 http://dojotoolkit.org/
祝你好运哥们,我希望它可以帮助…
有没有办法使用POST / PUT / DELETE做一个JSONP请求?
没有没有。
不,考虑一下JSONP是什么:在文档中注入一个新的<script>
标签。 浏览器执行一个GET
请求来拉取由src
属性指向的脚本。 这样做时,没有办法指定任何其他的HTTP动词。
- 而不是用JSONP方法来敲打我们的脑袋,实际上默认情况下不会支持POST方法,我们可以去CORS ,这对传统的编程方式没有太大的改变。 通过简单的Jquery Ajax调用,我们可以去跨域。
- 在CORS方法中,您必须在服务器端脚本文件或服务器本身(远程域)中添加标头,以启用此访问。 这是非常可靠的,因为我们可以防止/限制域名进行不受欢迎的电话。
- 它可以在维基百科页面中find。