如何使用Coldfusion CFHTTP将JSON数据发布到远程API

时间:2012-01-19 20:41:42

标签: json coldfusion cfhttp

我确信我已经完全搞砸了,但是在Stack Overflow用户的帮助下我得到了这么多,所以感谢到目前为止。

我需要将JSON数据POST到远程API。显然由于SOP问题我不能使用jQuery,而且远程API不支持JSONP。

我也不想使用任何类型的代理来解决SOP限制。

根据API文档(http://myemma.com/api-docs/),这是他们期望的数据格式(请求和响应数据作为JSON传输):

POST https://api.e2ma.net//123/members/add
{
  "fields": {
    "first_name": "myFirstName"
  }, 
  "email": "email@domain.com"
}

这是我迄今为止构建的内容,但仍然从远程API继续收到“无法解析JSON”错误:

<cfset fields[name_first]="#SerializeJSON( "myFirstName" )#" />
<cfset form.email="#SerializeJSON( "email@domain.com" )#" />

<cfhttp
  url="https://api.e2ma.net/123/members/add"
  method="POST"
  username="username"
  password="pssword"
  useragent="#CGI.http_user_agent#"
  result="objGet">

  <!--- add email --->
  <cfhttpparam
    type="formfield"
    name="email"
    value='#form.email#'
  />

  <!--- add field: name_first --->
  <cfhttpparam
    type="formfield"
    name="fields"
    value='#fields[name_first]#'
  />

</cfhttp>

<cfoutput>#objGet.FileContent#</cfoutput>

同样,我肯定会以某种方式破坏我的数据结构,但我不确定我做错了什么,特别是关于正确设置“字段”:{“first_name”:“myFirstName” 结构/数组。

5 个答案:

答案 0 :(得分:22)

您应该将请求字符串作为主体的httpparam类型发送。请求的主体可能类似于准备好的结构的整个表单范围。确保在隐式结构创建期间使用数组表示法来设置结构键或将它们放在“引号”中,以确保在serializeJSON()发生时它们保留正确的大小写,否则ColdFusion将使结构键大写。

<cfset stFields = {
    "fields" = {
        "first_name" = "myFirstName"
     }, 
     "email" = "email@domain.com"
}>   

<cfhttp url="http://api.url.com" method="post" result="httpResp" timeout="60">
    <cfhttpparam type="header" name="Content-Type" value="application/json" />
    <cfhttpparam type="body" value="#serializeJSON(stFields)#">
</cfhttp>

2013年10月26日更新
对于我最近使用API​​进行的所有工作,我认为我会更新一种简单的方法来自动化我发现的这种外壳。我使用了JSON Util库和Ben Nadel的JSON Serializer Utility CFC 的组合来为所有返回实现更好的序列化一致性。

下面是我如何实现这一点的示例GIST https://gist.github.com/timmaybrown/7226809

当我在我的项目中转换为使用持久性实体CFC时,我发现使用我自己的子CFC方法扩展Ben Nadel的序列化程序CFC,该方法使用getComponentMetaData()函数循环所有持久性cfc的属性一个不同的键结构和序列化的套管。该方法允许我的api自动继承我的实体中的属性名称的大小,非常有用。重新启动需要一些开销,但非常值得在您的API中保持套管一致。

更新9/8/16 Re:关于一致套管我的观点。我已经在我的数据库中针对新项目采用了不同的列命名约定,因此我不必为很多这些问题而斗争。 first_name代替firstName等。

答案 1 :(得分:8)

更新时间:2012年9月26日:在我设置的模拟帐户请求API密钥后,他们发送了一个以及may account_id。我将代码放在下面,它就像添加成员的魅力一样。

首先让我说这个代码没有经过测试 (参见上面的更新)。我没有MyEmma帐户,显然您必须是account_id的付费客户才能使用API​​。那吹!但是这应该让你真正接近并且可能会给你一些封装逻辑的想法,这已成为我的痴迷。

其次,我意识到这篇文章已经有9个月了,你可能要么已经想好了,要么赢了彩票并且现在正在经营这个地方。所以没有人可能会看到这篇文章。但是我一直在寻找一些答案,并且遇到了它...而且因为制定和解析JSON是我日常生活的一部分,所以我总是需要继续自我设定。所以结果是对你的问题的快速回答,成为一个深夜,自我服务,强迫性的挑战。无论如何......

...您正在使用JSON,正在创建客户端嵌套结构。您具有两个键值对(字段和电子邮件)的根结构。然后,结构“字段”包含一个结构,其中包含您为该电子邮件地址(first_name)发送的键值对。想必你可以发送更多。

您正在构建嵌套结构。请记住,结构中的键可以包含结构。这些键可以容纳结构,等等。它可以变得像你想要的那样黑暗和讨厌。但这就是所有JSON都是......它是客户端对象。

所以这是你的数据构建和JSON对象......

<cfscript>
    variables.dataFields = {};
    variables.dataFields['fields'] = {};
    variables.dataFields['email'] = "email@domain.com";
    variables.dataFields.fields['first_name'] = "myFirstName";
    variables.dataFields = serializejson(variables.dataFields);
</cfscript>

请注意,我是使用数组表示法显式设置结构键名。我们必须这样做来控制Coldfusion的情况。否则,密钥将全部封顶...不希望我们想要区分大小写的JavaScript。这可能是您遇到的问题的一部分。

如果Emma因为案件而不理解,那么你会得到你的......

{"error": "Unable to parse JSON request"}

但是当我们使用数组表示法显式设置我们的键名,然后序列化我们的对象时,我们会变得漂亮,漂亮,时尚的JSON ......

{"fields":{"first_name":"myFirstName"},"email":"email@domain.com"}

下面,我将我们的http请求放入一个函数中的Emma。 将Content-Type标头设置为application / json 也非常重要,因此浏览器会将其作为对象发送,而不仅仅是文本字符串。并且 我们将JSON作为我们请求的主体发送, 不在名为“fields”的表单字段中...希望当你大声说出来时这是有意义的。这是函数......

<cffunction name="callEmma" access="private" displayname="CallEmma" description="This makes an HTTP REQUEST to MyEmma" returnformat="JSON" output="false" returntype="Any">
    <cfargument name="endpoint" required="true" type="string" displayname="EndPoint">
    <cfargument name="PUBLIC_API_KEY" required="true" type="string" displayname="PUBLIC_API_KEY">
    <cfargument name="PRIVATE_API_KEY" required="true" type="string" displayname="PRIVATE_API_KEY">
    <cfargument name="dataFields" required="true" type="struct" displayname="DataFields">
    <cfscript>
        local = {};
        local.baseURL = "https://api.e2ma.net/";
        local.account_id = "12345";
        local.phoneNumber = local.baseURL & local.account_id & arguments.endPoint;
        local.connection = new http();
        local.connection.setMethod("POST"); 
        local.connection.setUrl(local.phoneNumber);
        local.connection.setUsername(arguments.PUBLIC_API_KEY);
        local.connection.setPassword(arguments.PRIVATE_API_KEY);
        local.connection.setUserAgent(cgi.http_user_agent);
        local.connection.addParam(type="header",name="Content-Type", value="application/json");
        local.connection.addParam(type="body", value=arguments.dataFields); 
        local.objGet = local.connection.send().getPrefix();
        local.content = local.objGet.filecontent;
        return local.content
    </cfscript>
</cffunction>

然后再一次,这是我们的JSON构建(嵌套结构)......

<cfscript>
    variables.dataFields = {};
    variables.dataFields['fields'] = {};
    variables.dataFields['email'] = "email@domain.com";
    variables.dataFields.fields['first_name'] = "myFirstName";
    variables.dataFields = serializejson(variables.dataFields);
</cfscript>

然后我们将变量设置为传递给函数...

<cfscript>
    variables.entryPoint = "/members/add";
    variables.PUBLIC_API_KEY= "PUBLIC_API_KEY";
    variables.PRIVATE_API_KEY= "PRIVATE_API_KEY";
</cfscript>

然后拨打电话......

<cfscript>
    variables.myResponse = callEmma(variables.entryPoint,variables.PUBLIC_API_KEY,variables.PRIVATE_API_KEY,variables.dataFields);
    variables.myResponse = deserializejson(variables.myResponse);
</cfscript>

然后我们接受我们的响应,反序列化它,然后输出我们想要的变量。

<cfscript>
    if(variables.myResponse.added){
        writeoutput("Member " & variables.myResponse.member_id & " added!");
    }
    else{
        writeoutput("There was an error adding this member");
    }
</cfscript>

我通常尽可能多地使用<cfscript>。它更容易阅读,它让我感觉比我真的更聪明。因此,当我们把所有这些放在一起时,为了剪切和粘贴,我们有这个......

<cfscript>
// Function to make our calls to Emma
private any function callEmma(required string endPoint,required string PUBLIC_API_KEY,required string PRIVATE_API_KEY,required string dataFields)
    description="This makes an HTTP REQUEST to MyEmma"
    displayname="CallEmma"
    returnformat="JSON"
    output="false"
{
    local = {};
    local.baseURL = "https://api.e2ma.net/";
    local.account_id = "12345";
    local.phoneNumber = local.baseURL & local.account_id & arguments.endPoint;
    local.connection = new http();
    local.connection.setMethod("POST"); 
    local.connection.setUrl(local.phoneNumber);
    local.connection.setUsername(arguments.PUBLIC_API_KEY);
    local.connection.setPassword(arguments.PRIVATE_API_KEY);
    local.connection.setUserAgent(cgi.http_user_agent);
    local.connection.addParam(type="header",name="Content-Type", value="application/json");
    local.connection.addParam(type="body",value=arguments.dataFields); 
    local.objGet = local.connection.send().getPrefix();
    local.content = local.objGet.filecontent;
    return local.content;
} 

// Put our data together
variables.dataFields = {};
variables.dataFields['fields'] = {};
variables.dataFields['email'] = "email@domain.com";
variables.dataFields.fields['first_name'] = "myFirstName";
variables.dataFields = serializejson(variables.dataFields);

// Define the parameters for our call to Emma
variables.entryPoint = "/members/add";
variables.PUBLIC_API_KEY= "PUBLIC_API_KEY";
variables.PRIVATE_API_KEY= "PRIVATE_API_KEY";

// Call Emma
variables.myResponse = callEmma(variables.entryPoint,variables.PUBLIC_API_KEY,variables.PRIVATE_API_KEY,variables.dataFields);
variables.myResponse = deserializejson(variables.myResponse);

//Output to browser
if(variables.myResponse.added){
    writeoutput("Member " & variables.myResponse.member_id & " added!");
}
else{
    writeoutput("There was an error adding this member");
}
</cfscript>

我的上帝!我一直在写太多的API ......我显然需要治疗!

答案 2 :(得分:1)

您提到的结构

{       “fields”:{         “first_name”:“myFirstName”       },       “email”:“email@domain.com”     }     在这个'fields'的JSON中,键值也是一个JSON     所以,你可以像dis

一样
<cfscript>
        VARIABLES.postJSON = StructNew();
        VARIABLES.nameJSON = StructNew();
        StructInsert(VARIABLES.nameJSON, 'first_name','myFirstName');
        StructInsert(VARIABLES.postJSON, 'fields',VARIABLES.nameJSON);
        StructInsert(VARIABLES.postJSON, 'email','email@domain.com');

</cfscript> 

<cfhttp
  url="https://api.e2ma.net/123/members/add"
  method="POST"
  username="username"
  password="pssword"
  useragent="#CGI.http_user_agent#"
  result="objGet">

  <cfhttpparam
    type="body"
    name="field"
    value='#SerializeJSON(VARIABLES.postJSON)#'
  />

</cfhttp>

<cfoutput>#objGet.FileContent#</cfoutput>

答案 3 :(得分:0)

鉴于您提交数据的方式,您不必序列化字符串,只需

value='#serializejson(fields)#'

根据您的评论,这对您不起作用。不幸的是,他们的文档让IMO对如何发送数据感到困惑。他们说它应该是一个帖子,但后来只显示一个json对象。如果从JS中使用它可能会有用,但在其他情况下则会令人困惑。

要缩小问题发生的位置,请尝试静态提交信息,例如,获取示例代码并粘贴到字段的值中。您应该首先尝试在动态版本之前进行静态尝试。由于区分大小写或其他问题,甚至可能是CF json序列化正在绊倒。

<!--- add email --->
<cfhttpparam
  type="formfield"
  name="email"
  value='email@domain.com'
/>

<!--- add field: name_first --->
<cfhttpparam
  type="formfield"
  name="fields"
  value='{ "first_name": "myFirstName" }'
/>
<!--- or if that doesn't work also try value='"first_name": "myFirstName" ' --->

答案 4 :(得分:0)

幸运的时机。因为我们目前正在解决同样的问题。

我们目前正在努力将我们的CF版本从8更新到9.01,并且有一些使用cfajaxproxy的代码 - 无法在9.01下运行 - 但在CF8中工作正常。

我尚未决定(1)该问题的实际根本原因是什么; 如果我有时间,我会做更多的工作来更具体...... 但解决方法是将通过ajax调用的代码放在webroot中。

(1)它可能是由于使用虚拟目录引起的,或者可能是由CF应用程序框架引起的 - CFIDE脚本会自动插入到文件中 - 并且会返回预期的JSON格式。

我已经记录了Adobe的错误。