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

如何使用Coldfusion CFHTTP将JSON数据发布到远程API,json,coldfusion,cfhttp,Json,Coldfusion,Cfhttp,我确信我完全搞砸了,但我在其他堆栈溢出用户的帮助下走到了这一步,所以到目前为止,谢谢 我需要将JSON数据发布到远程API。显然,由于SOP问题,我不能使用jQuery,而且远程API不支持JSONP 我也不想使用任何类型的代理来绕过SOP限制 根据API文档(),这是他们期望的数据格式(请求和响应数据作为JSON传输): POST https://api.e2ma.net//123/members/add { "fields": { "first_name": "myFirstNa

我确信我完全搞砸了,但我在其他堆栈溢出用户的帮助下走到了这一步,所以到目前为止,谢谢

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

我也不想使用任何类型的代理来绕过SOP限制

根据API文档(),这是他们期望的数据格式(请求和响应数据作为JSON传输):

POST https://api.e2ma.net//123/members/add
{
  "fields": {
    "first_name": "myFirstName"
  }, 
  "email": "email@domain.com"
}
<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>
这是我迄今为止构建的,但仍然从远程API收到“无法解析JSON”错误:

POST https://api.e2ma.net//123/members/add
{
  "fields": {
    "first_name": "myFirstName"
  }, 
  "email": "email@domain.com"
}
<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>

#objGet.FileContent#

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

考虑到提交数据的方式,不必序列化字符串,只要

value='#serializejson(fields)#'
从你的评论来看,这不适合你。不幸的是,他们的文件让IMO对数据应该如何发送感到困惑。他们说它应该是一个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" ' --->

偶然的时机。因为我们目前正在处理同一问题

我们目前正在努力将CF版本从8更新到9.01,并且有一些代码使用了cfajaxproxy——无法在9.01下运行——但在CF8中运行良好

我不确定(1)问题的实际根源是什么; 如果我有时间,我会做一些更具体的工作。。。 但解决方法是将通过ajax调用的代码放在webroot中

(1) 这可能是由于使用虚拟目录造成的,也可能是受到CF应用程序框架的影响(通过该框架,CFIDE脚本会自动插入到文件中),并且会与返回的JSON的预期格式发生冲突


我在Adobe上记录了一个bug。

您应该将请求字符串作为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方面所做的所有工作,我想我应该更新一种简单的方法来自动化我发现的这个外壳。我使用了库和Ben Nadel的组合来为所有返回实现更好的序列化一致性

下面是我如何实现此功能的示例要点。

当我在我的项目中过渡到使用持久实体CFC时,我发现用我自己的子CFC方法扩展Ben Nadel的序列化程序CFC,该方法使用函数循环我的所有持久CFC属性,以构建不同键的结构和序列化所遵循的大小写。这种方法允许api自动继承实体中属性名的大小写,非常有用。在reinit上有一点开销,但在API中保持套管一致是值得的

2016年8月9日更新
Re:我上面关于一致性套管的观点。对于较新的项目,我倾向于在数据库中使用不同的列命名约定,这样我就不必为这些问题争论太多
first\u name
而不是
firstName
等。

更新:2012年9月26日:在请求使用我设置的演示帐户的API密钥后,他们向我发送了一个API密钥以及may帐户id。我将代码放在下面,这就像添加成员的魅力一样。

让我首先说,这些代码都没有经过测试(请参阅上面的更新)。我没有MyEmma帐户,显然您必须是帐户id的付费客户才能使用API。太棒了!但这应该会让你真正接近,并可能给你一些封装逻辑的想法,这已经成为我的困扰

第二,我意识到这篇文章已经9个月了,你可能早就明白了,或者中了彩票,现在已经开始经营这个地方了。所以没人会看到这篇文章。但我自己也在寻找答案,然后就遇到了。。。由于制定和解析JSON是我日常生活的一部分,这是我一直需要坚持的事情。所以,对你的问题的快速回答变成了一个深夜的、自私的、强迫性的挑战。无论如何

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

您正在构建嵌套结构。请记住,结构中的键可以容纳结构。这些键可以保存结构,等等。它可以变得黑暗和肮脏,因为你想去。但这就是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>
但是,当我们使用数组表示法显式地设置键名,然后序列化对象时,我们得到了漂亮的、好的ol’fashion JSON

{"fields":{"first_name":"myFirstName"},"email":"email@domain.com"}
下面,我将我们的http请求放到
<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>
// 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>
<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>