Как отправить POST данные JSON в удаленный API с помощью ColdFusion CFHTTP

Я уверен, что я полностью этим занимаюсь, но я добрался до этого с помощью других пользователей Stack Overflow, так что спасибо до сих пор.

Мне нужно передать данные POST JSON в удаленный API. Очевидно, что я не могу использовать jQuery из-за проблем SOP, а удаленный API не поддерживает JSONP.

Я также не хочу использовать какой-либо прокси-сервер, чтобы обойти ограничения SOP.

В документах API (http://myemma.com/api-docs/) это форматирование ожидаемых данных (данные запроса и ответа передан как JSON):

POST https://api.e2ma.net//123/members/add
{
  "fields": {
    "first_name": "myFirstName"
  }, 
  "email": "[email protected]"
}

И это то, что я создал до сих пор, но продолжаю получать ошибки удаленного анализа JSON из удаленного API:

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

<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 " } структура/массив.

Ответ 1

Вы должны отправить свою строку запроса как тип httpparam тела. Тело запроса может быть чем-то вроде всей области формы вашей подготовленной структуры. Не забудьте использовать нотацию массива для установки ваших структурных ключей или поместить их в "кавычки" во время создания неявной структуры, чтобы гарантировать, что они сохраняют свой собственный корпус, когда выполняется serializeJSON(), в противном случае ColdFusion будет задерживать ключи структуры.

<cfset stFields = {
    "fields" = {
        "first_name" = "myFirstName"
     }, 
     "email" = "[email protected]"
}>   

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

Обновление 10/26/13
Для всей работы, которую я делал в последнее время с API-интерфейсами, мне показалось, что я обновляю простой способ автоматизации этого корпуса, который я нашел. Я использовал комбинацию JSON Util и Ben Nadel JSON Serializer Утилита CFC, чтобы добиться большей согласованности последовательности для всех возвратов.

Ниже приведен пример GIST того, как я реализовал это.
https://gist.github.com/timmaybrown/7226809

Как я перешел к использованию постоянных сущностей CFC в своих проектах, я обнаружил, что расширение сериализатора на основе Ben Nadel CFC с помощью моего собственного метода CFC для детей, который объединяет все мои постоянные свойства cfc с помощью getComponentMetaData() для создания структуры отдельных ключей и оболочки для последовательной последовательной обработки. Подход позволяет моему api автоматически наследовать оболочку имен моих свойств внутри моих объектов и очень полезен. Немного накладных расходов на повторное создание, но стоит того, чтобы ваш корпус был совместим с вашим API.

Обновление 9/8/16 Re: моя точка выше о согласованной оболочке. Я склонялся к другому соглашению об именах столбцов в моих базах данных для более новых проектов, поэтому мне не нужно бороться с множеством этих проблем. first_name вместо firstName и т.д.

Ответ 2

ОБНОВЛЕНИЕ: 9/26/2012: после запроса ключа 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 protected]";
    variables.dataFields.fields['first_name'] = "myFirstName";
    variables.dataFields = serializejson(variables.dataFields);
</cfscript>

Обратите внимание, что я явно устанавливаю имена структурных ключей с помощью нотации массива. Мы должны это сделать, чтобы контролировать дело с Coldfusion. В противном случае ключи будут во всех кешках... мы не хотим, чтобы JavaScript-код был чувствителен к регистру. Это может быть частью проблемы, с которой вы сталкиваетесь.

Если Эмма не понимает из-за случая, тогда вы получите свой...

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

Но когда мы явно устанавливаем наши имена ключей, используя нотацию массива, а затем сериализуем наш объект, мы получаем красивую и красивую, хорошую моду JSON...

{"fields":{"first_name":"myFirstName"},"email":"[email protected]"}

Итак, ниже, я передал наш запрос http Emma в функцию. Также важно установить заголовок Content-Type как application/json, поэтому браузер отправит его как объект, а не только текстовую строку. И мы отправляем наш JSON как тело нашего запроса не в поле формы под названием "поля"... надеюсь, это имеет смысл, когда вы говорите это вслух. Здесь функция...

<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 protected]";
    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 protected]";
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... Мне явно нужна терапия!

Ответ 3

Структура, о которой вы упоминали

{      "поля": {        "first_name": "myFirstName"     },      "email": "[email protected]"   }   В этом 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 protected]');

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

Ответ 4

Учитывая то, как вы отправляете данные, вам не нужно сериализовывать строки, просто

value='#serializejson(fields)#'

Из вашего комментария, это не сработало для вас. К сожалению, их документы запутывают ИМО относительно того, как следует отправлять данные. Говорят, что это должно быть сообщение, но потом показывать только объект json. Может быть, это полезно, если использовать JS, но путать иначе.

Чтобы сузить место возникновения проблемы, попробуйте отправить информацию статически, например, возьмите свой примерный код и вставьте в значения полей. Сначала вы должны попытаться сделать статическую попытку перед динамической версией. Возможно даже, что сериализация CF json отключает все из-за чувствительности к регистру или других проблем.

<!--- add email --->
<cfhttpparam
  type="formfield"
  name="email"
  value='[email protected]'
/>

<!--- 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" ' --->

Ответ 5

Непосредственное время. В этом мы в настоящее время работаем по одной и той же проблеме.

В настоящее время мы работаем над обновлением нашей версии CF с 8 по 9 января и имеем код, который использует cfajaxproxy, который не работает под 9.01, но отлично работает в CF8.

Я не определился (1) относительно того, какова фактическая первопричина проблемы; Если я получу некоторое время, я сделаю еще одну работу, чтобы быть более конкретным... но обходным путем является код, который вызывается через ajax в webroot.

(1) это может быть вызвано использованием виртуальных каталогов или, возможно, осуществляется с помощью фреймворка CF Application, при котором скрипты CFIDE автоматически вставляются в файлы - и беспорядочно возвращаются ожидаемый формат JSON.

Я зарегистрировал ошибку с Adobe.