Как отправить данные JSON в удаленный API с помощью Coldfusion CFHTTP - PullRequest
13 голосов
/ 20 января 2012

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

Мне нужно отправить данные 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@domain.com"
}

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

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

22 голосов
/ 04 февраля 2012

Вы должны отправить строку запроса в качестве типа тела 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>

Обновление 10/26/13
При всей работе, которую я выполнял в последнее время с API, я подумал, что обновлю простой способ автоматизации этого корпуса, который я нашел,Я использовал комбинацию JSON Util библиотеки и CFC служебной программы JSON Serializer Бена Наделя *1000*, чтобы добиться гораздо лучшей согласованности сериализации для всех возвращаемых значений.

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

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

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

8 голосов
/ 25 сентября 2012

ОБНОВЛЕНИЕ: 26.09.2012: После запроса ключа API для демо-счета, который я настроил, мне прислали его вместе с may account_id.Я опустил код ниже, и он работал как талисман для добавления члена.

Позвольте мне начать с того, что ни один из этого кода не протестирован (см. Обновлениевыше) .У меня нет учетной записи MyEmma, ​​и, очевидно, вы должны быть платящим клиентом для account_id, чтобы использовать API.Это дует!Но это должно приблизить вас к реальности и может дать вам некоторые идеи для инкапсуляции логики, которая стала моей навязчивой идеей.

Во-вторых, я понимаю, что этому посту уже 9 месяцев, и вы, вероятно, либо давно его поняли, либовыиграл в лотерею и сейчас управляет этим местом.Так что никто никогда не увидит этот пост.Но я сам искал ответы на некоторые вопросы и наткнулся на них ... и, поскольку формулирование и анализ JSON - это часть моей повседневной жизни, это то, что мне всегда нужно постоянно настраивать.Таким образом, то, что оказалось быстрым ответом на ваш вопрос, стало поздним вечером, корыстным, одержимым вызовом.В любом случае ...

... то, что вы делаете с JSON, - это создание вложенных структур на стороне клиента.У вас есть корневая структура с двумя парами ключ-значение (поля и адрес электронной почты).Затем структура '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-код с учетом регистра.Это может быть частью проблемы, с которой вы столкнулись.

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

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

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

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

Итак, ниже я помещаю наш http-запрос вЭмма в функции.Также очень важно установить заголовок 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@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>

Затем мы берем наш ответ, десериализуем его и выводим переменные так, как хотим.1053 *

Больше я обычно использую <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 голос
/ 26 августа 2014

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

{"fields": {"first_name": "myFirstName"}, "email": "email@domain.com"} В этом JSON для значения ключа 'fields'снова 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>
0 голосов
/ 20 января 2012

Случайный выбор времени. В этом мы сейчас работаем над той же проблемой.

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

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

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

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

0 голосов
/ 20 января 2012

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

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