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

Я уверен, что я полностью испортил это, но я дошел до этого с помощью других пользователей 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 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"} структура / массив.


person goxmedia    schedule 19.01.2012    source источник


Ответы (5)


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

Обновление 26.10.13
Несмотря на всю работу, которую я проделал в последнее время с API, я подумал, что обновлю простой способ автоматизации этого корпуса, который я нашел. Я использовал комбинацию библиотеки JSON Util и Утилита CFC для сериализатора JSON для обеспечения большей согласованности сериализации для всех. возвращается.

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

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

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

person timbrown    schedule 04.02.2012
comment
Если в ходе сериализации с помощью coldfusion возникают какие-либо проблемы, вы также можете просто создать тело в строке, например ‹cfset stFields = '{fields = {first_name = # firstname #}, email = # email #}' /› - person Dan Roberts; 10.02.2012
comment
да, или исправьте некоторые проблемы сериализации json в CF, при желании используя ссылку проекта 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 protected]"} - person timbrown; 11.02.2012
comment
@ user1113083 - если это было полезно, вы должны отметить это как правильный ответ для других. - person timbrown; 07.02.2013
comment
Это именно то, что мне нужно для вызова внутреннего API, для которого у меня не было доступа к WSDL. Это сработало отлично. Спасибо! - person Mark; 01.04.2013
comment
@dadwithkids рад, что это помогло вам. Сериализация JSON в CF довольно проста, используя имена ключей в кавычках в неявном создании массива / структуры и javaCast () для определенных значений по мере необходимости, вы можете легко обеспечить правильную сериализацию. - person timbrown; 16.04.2013
comment
Обновленный ответ с дополнительным подходом к управлению корпусом сериализации в Coldfusion ... - person timbrown; 30.10.2013
comment
@timbrown Не могли бы вы помочь здесь: stackoverflow.com/questions/23274469/ - person Nich; 24.04.2014
comment
@nich - конечно, но я думаю, вы догадались и удалили свой вопрос. Извините, я не видел этого раньше. - person timbrown; 30.05.2014
comment
@Gregory Matthews - если этот вопрос помог вам решить вашу проблему, отметьте его как принятый, чтобы помочь другим. - person timbrown; 27.11.2018
comment
@timbrown Perfect. Работает на меня :) - person U.Malik; 11.12.2019

ОБНОВЛЕНИЕ: 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 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-запрос к Эмме в функцию. Также очень важно установить заголовок 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 ... Мне явно нужна терапия!

person Steve Reich    schedule 25.09.2012

Упомянутая вами структура

{fields: {first_name: myFirstName}, email: [email protected]} В этом 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 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>
person krishna Ram    schedule 26.08.2014

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

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

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

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

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

person Gavin Baumanis    schedule 20.01.2012
comment
Вы описываете другую проблему, но я ценю ваш вклад. - person goxmedia; 20.01.2012

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

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" ' --->
person Dan Roberts    schedule 20.01.2012
comment
Я понимаю, что вы говорите, но я все еще получаю сообщение {error: Unable to parse JSON request} обратно из API. Это означает, что я неправильно отправляю параметры email: [email protected] или fields: {first_name: myFirstName} в моем примере выше. Вот с чем мне нужна помощь. Правильная передача этих строк JSON. - person goxmedia; 20.01.2012