使用PUT/POST/DELETE与JSONP和jQuery

rya*_*zec 38 javascript rest jquery web-services

我正在创建一个支持跨域请求,JSON/JSONP支持和主HTTP方法(PUT/GET/POST/DELETE)的RESTful API.现在虽然很容易通过服务器端代码访问此API,但将它暴露给javascript会很好.据我所知,当使用jQuery执行JSONP请求时,它只支持GET方法.有没有办法使用POST/PUT/DELETE执行JSONP请求?

理想情况下,我想在jQuery中使用一种方法(如果内核不支持这个,则通过插件),但我也会采用简单的javascript解决方案.任何指向工作代码的链接或如何编写代码都会有所帮助,谢谢.

Rad*_*own 70

实际上 - 有一种方法可以支持POST请求.并且在PROXI服务器中没有必要 - 只是一个小实用程序HTML页面,如下所述.

以下是如何有效地进行POST跨域调用,包括附加文件和多部分以及所有:)

首先是理解这个想法的步骤,然后找到一个实现样本.

如何实现jQuery的JSONP,为什么它不支持POST请求?

虽然传统的JSONP是通过创建脚本元素并将其附加到DOM中来实现的 - 但是导致浏览器触发HTTP请求以检索标记的源,然后将其作为JavaScript执行,这是浏览器触发的HTTP请求很简单GET.

什么不仅限于GET请求?

表单.在action指定跨域服务器的同时提交FORM .可以使用脚本完全创建FORM标记,使用脚本填充所有字段,设置所有必需的属性,注入DOM,然后提交 - 全部使用脚本.

但是,如何在不刷新页面的情况下提交表格?

我们target在同一页面中指定IFRAME的表单.还可以使用脚本创建,设置,命名和注入IFRAME到DOM.

但是我们怎样才能将这项工作隐藏起来呢? 我们将在隐藏的DIV中包含FORM和IFRAMEstyle="display:none"

(这是技术中最复杂的部分,请耐心等待)

但来自另一个域的IFRAME无法在其顶层文档上调用回调.如何克服这个?

实际上,如果FORM submit的响应是来自另一个域的页面,则顶级页面与IFRAME中的页面之间的任何脚本通信都会导致"访问被拒绝".因此服务器无法使用脚本进行回调.服务器可以做什么?重定向.服务器可以重定向到任何页面 - 包括与顶级文档位于同一域中的页面 - 可以为我们调用回调的页面.

服务器如何重定向?

两种方式:

  1. 使用客户端脚本 <Script>location.href = 'some-url'</script>
  2. 使用HTTP-Header.请参阅:http://www.webconfs.com/how-to-redirect-a-webpage.php

所以我最终得到另一页?它对我有什么帮助?

这是一个简单的实用程序页面,将用于所有跨域调用.实际上,这个页面实际上是一种proxi,但它不是一个服务器,而是一个简单的静态HTML页面,任何有记事本和浏览器的人都可以使用.

所有这一页所要做的就是使用服务器的响应数据调用顶级文档的回调.客户端脚本可以访问所有URL部分,服务器可以将其响应编码为其中的一部分,以及必须调用的回调的名称.意味着 - 此页面可以是静态和HTML页面,而不必是动态的服务器端页面:)

该实用程序页面将从其运行的URL中获取信息 - 特别是在我的实现中 - 查询字符串参数(或者您可以使用anchor-ID编写自己的实现 - 即URL的一部分直接到"#"标志).因为这个页面是静态的 - 它甚至可以被缓存:)

不会为每个POST请求添加DIV,SCRIPT和IFRAME最终泄漏内存?

如果你将它留在页面中 - 它会.如果你在你之后清洁 - 它不会.我们所要做的就是给DIV提供一个ID,我们可以用它来响应DIV以及响应从服务器到达时的FORM和IFRAME,或者超时.

我们得到了什么?

有效地进行POST跨域调用,包括附加文件和多部分以及所有:)

有什么限制?

  • 服务器响应仅限于适合重定向的任何内容.
  • 服务器必须始终将REDIRECT返回到POST请求.这包括404和500错误.或者 - 在触发请求之前在客户端上创建超时,这样您就有机会检测未返回的请求.
  • 不是每个人都能理解所有这些以及所涉及的所有阶段.它是一种基础架构级别的工作,但一旦你运行它 - 它摇滚:)

我可以将它用于PUT和DELETE呼叫吗?

FORM标签不是PUT和DELETE.但那比没有好:)

好的,得到了​​这个概念.它是如何在技术上完成的?

我所做的是:

我创建了DIV,将其设置为不可见,并将其附加到DOM.我还给它一个ID,我可以在服务器响应到达后从DOM中清除它(就像JQuery清除它的JSONP SCRIPT tasgs - 但是DIV一样).

然后我编写一个包含IFRAME和FORM的字符串 - 包含所有属性,属性和输入字段,并将其注入不可见的DIV.只有在div在DOM中之后,才将此字符串注入DIV非常重要.如果不是 - 它将不适用于所有浏览器.

之后 - 我获得了对FORM的引用并提交.请记住之前的一行 - 设置Timeout回调以防服务器没有响应,或以错误的方式响应.

回调函数包含清理代码.在响应超时的情况下,定时器也会调用它(并在服务器响应到达时清除它的超时定时器).

告诉我代码!

下面的代码片段对"纯粹的"javascript完全"中立",并声明它需要的任何实用程序.只是为了简化解释这个想法 - 它全都在全球范围内运行,但它应该更复杂......

尽可能在函数中组织它并参数化你需要的东西 - 但要确保所有需要看到彼此的部分在相同的范围内运行:)

对于此示例 - 假设客户端在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,"&quot;");//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();
Run Code Online (Sandbox Code Playgroud)

跨域上 的服务器来自服务器的响应应该是一个REDIRECTION,可以是HTTP-Header,也可以是写一个SCRIPT标签.(重定向更好,使用JS断点更容易调试SCRIPT标记).这是标题的示例,假设rurl从上面的值

Location: http://samedomain.com/HTML/page?callback=myAsyncJSONPCallback&data=whatever_the_server_has_to_return
Run Code Online (Sandbox Code Playgroud)

注意

  • data参数的值可以是JavaScript Object-Literal或JSON表达式,但最好是url编码.
  • 服务器响应的长度限制为浏览器可以处理的URL的长度.

另外 - 在我的系统中,服务器有一个默认值,rurl因此该参数是可选的.但是只有在客户端应用程序和服务器应用程序耦合时才能这样做.

用于发出重定向标头的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>
Run Code Online (Sandbox Code Playgroud)

但HTTP-Headers会被认为更干净;)

实用程序页面与顶级文档位于同一域中

我使用与rurl我的所有发布请求相同的实用程序页面:它所做的只是使用客户端代码获取回调的名称和Query-String中的参数,并在父文档上调用它.它只能在此页面与触发请求的页面的EXACT相同域中运行时才能执行此操作!重要提示:与Cookie不同 - 子域名不计算!! 它必须与他完全相同的域名.

如果此实用程序页面不包含对其他资源的引用(包括JS库),它也会提高效率.所以这个页面是纯JavaScript.但是你可以随心所欲地实现它.

这是我使用的响应者页面,rurl在POST请求中找到了谁的URL (在示例中:http://samedomain.com/utils/postResponse.html)

<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>
Run Code Online (Sandbox Code Playgroud)

这不是很多自动化和'现成的'库像jQuery和involes一些'手动'工作 - 但它有魅力:)

如果你是现成库的忠实粉丝 - 你也可以查看Dojo Toolkit,当我最后一次检查时(大约一年前) - 有相同机制的自己的实现. http://dojotoolkit.org/

祝你好运好友,我希望它有所帮助......

  • 这实际上是想象力和技术诀窍的壮观运用.好样的! (5认同)

Dar*_*rov 13

有没有办法使用POST/PUT/DELETE执行JSONP请求?

不,没有.

  • @Darin Dimitrov,也许他正在寻找关于除了答案之外他应该尝试什么的建议。 (2认同)

Way*_*ett 8

不.考虑JSONP是什么:<script>在文档中注入新标记.浏览器执行GET拉取src属性指向的脚本的请求.这样做时无法指定任何其他HTTP动词.