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

15

我确定我完全搞砸了这个,但是在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>

我肯定是在某种程度上弄乱了我的数据结构,但我不确定我做错了什么,特别是关于正确设置"fields": {"first_name": "myFirstName"} 结构/数组。

5个回答

30

你应该将请求字符串作为“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,我发现通过使用getComponentMetaData()函数循环遍历我所有持久性CFC属性的自己的子CFC方法扩展Ben Nadel的序列化程序CFC,可以构建一个独特键和序列化后格式的结构。该方法允许我的API自动继承实体中的属性名称的大小写,并非常有用。虽然reinit时开销有点大,但为了使API中的大小写一致性保持一致,这样做是非常值得的。

更新于2016年9月8日:
关于我上面提到的大小写一致性问题。对于新项目,我趋向于在数据库中使用不同的列命名约定,以便不必与许多这些问题作斗争。例如,使用first_name而不是firstName等。


如果在序列化过程中ColdFusion出现任何问题,您也可以使用字符串构建主体,例如< cfset stFields =' "{ "fields" = { "first_name" = "# firstname #" }, "email" = "# email #" } '/>。 - Dan Roberts
1
嗯,或者修复CF中的一些JSON序列化问题,可以选择使用JSONUtil项目链接。它有一个选项用于严格映射键的大小写敏感性。此外,在某些情况下,使用javaCast('Boolean', 'true')将确保在序列化过程中将其设置为布尔值而不是字符串。<cfset stFields = { "fields" = { "first_name" = "myFirstName", "is_active" = javaCast('Boolean', true) } }>这将导致以下JSON字符串: {"fields":{"first_name":"myFirstName","is_active":true},"email":"email@domain.com"} - timbrown
更新的答案,包括在Coldfusion中控制序列化大小写的另一种方法... - timbrown
@Gregory Matthews - 如果这个问题帮助了您解决了问题,请将其标记为“接受的答案”,以帮助其他人。 - timbrown
@timbrown 很好。对我来说可行 :) - U.Malik
显示剩余5条评论

10

更新:2012年9月26日:在请求我设置的演示账户的API密钥后,他们向我发送了一个API密钥并附带我的账户ID。我将代码放在下面,它可以很好地用于添加成员。

首先,让我说一下,这段代码没有经过测试 (参见上面的更新)。我没有MyEmma账户,显然你必须是付费客户才能使用API的账户ID。这太糟糕了!但这应该让你接近实际情况,并可能给你一些封装逻辑的想法,这已经成为我的追求。

其次,我意识到这篇文章已经有9个月了,你可能已经长时间弄清楚了,或者已经赢得彩票并且现在正在运营这个地方。所以可能根本没有人会看到这篇文章。但我自己也在寻找答案,并偶然发现了它……由于构建和解析JSON是我日常生活的一部分,我总是需要保持清晰的思路。因此,对你的问题的快速回答变成了深夜的、自私的、着迷的挑战。无论如何……

……你正在使用JSON创建客户端嵌套结构。你有根结构,其中包含两个键值对(fields和email)。然后,'fields'结构保存了一个键值对,你正在为该电子邮件地址发送的内容(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"}

下面是我们向Emma发送http请求的函数。非常重要的一点是设置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了...我显然需要治疗!


1

您提到的结构

{ "fields": { "first_name": "我的名字" }, "email": "email@domain.com" } 在这个JSON中,'fields'键的值再次是一个JSON 所以,您可以像这样进行:

<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>

0

时机巧合,我们正在处理相同的问题。

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

我还没有确定问题的实际根本原因(1); 如果我有时间,我会做更多的工作以使其更具体......但解决方法是将通过ajax调用的代码放在WebRoot中。

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

我已向Adobe报告了一个错误。


您描述的是另一个问题,但我很感谢您的意见。 - goxmedia

0

根据您提交数据的方式,您不需要对字符串进行序列化,只需

value='#serializejson(fields)#'

从您的评论来看,这对您没有起作用。不幸的是,他们的文档在我看来很令人困惑,因为它们没有明确说明数据应该如何发送。他们说应该是一个post请求,但只显示了一个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" ' --->

我理解你的意思,但是我仍然从 API 得到 {"error": "无法解析 JSON 请求"}的响应。这意味着我在上面的示例中没有正确提交 "email": "email@domain.com" 或 "fields": {"first_name": "myFirstName"} 参数。这就是我需要帮助的地方。正确传递这些 JSON 字符串。 - goxmedia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接