Найти в Дзене
Знания - сила!

Маленькие проблемы, которые возникают из-за неясностей в документации Яндекс Директ.

Начал писать своё приложение для Яндекс Директа (я и программист, и занимаюсь интернет-маркетингом, используя Яндекс Директ). Устал вручную делать некоторые операции и решил вспомнить о том, что пишу на нескольких языках программирования. Так как задачка задумана как консольная, то писать её на PHP (который я не особо и люблю...) или Python, смысла большого не было. Консольная потому, что запускаться будет из crontab-а, то есть по времени/расписанию.

Уважаю и пишу на Java (ещё с версии 0.99 альфа - 95 год?... - не помню). Решил и эту задачку писать на Java. Не на C++ же её писать? Можно, конечно, и на C налабать, но... Java лучше.

Начал разбираться в документации Директа и обнаружил, что в разных документах (фи в сторону Яндекса!) иногда написано по-разному... Как выяснить что правильно? Нормальный программист скажет - тестировать!

Залез в Директ, зарегистрировал приложение, получил токен. Осталось тестировать, то есть писать и пробовать. Посмотрел документацию, что нужно знать? Ага! JSON, которым не пользовался. Запросы к HTTP/HTTPS серверу. Тоже особо не занимался (специфика больше к RDBMS). Посмотрел на стандартный набор - JSON отсутствует. Поискал, нашёл много и разных реализаций, но так как тяготею к стандартам (java.*, javax.*), то выбрал jsonp реализацию. По-крайней мере, не надо мозг ломать - javax.json.* структура классов. Вроде всё легко и просто. Разобрался как сделать с помощью Java нужную структуру JSON.

Вот текстовик, чем разбирался (JsonObjGen.java):

package test;

import java.io.*;

import java.util.*;

import javax.json.*;

public class JsonObjGen

{

public static void main(String v[])

{

for(String s:v)

{

new JsonObjGen(s);

}

}

public JsonObjGen(String s)

{

StringTokenizer st = null;

JsonObject jo = null;

JsonObjectBuilder jb = Json.createObjectBuilder();

jb = jb.add("var", Json.createArrayBuilder().addNull());

System.out.println(jb.build());

jb = Json.createObjectBuilder().add("a", "b");

jo = jb.build();

System.out.println(jo);

jb = Json.createObjectBuilder().add("obj",

Json.createObjectBuilder().add("inside",

Json.createArrayBuilder().addNull()));

jo = jb.build();

System.out.println(jo);

jb = Json.createObjectBuilder().add("obje",

Json.createObjectBuilder().add("invar",

Json.createArrayBuilder().add(

Json.createObjectBuilder().addNull("inset"))));

jo = jb.build();

System.out.println(jo);

jb = Json.createObjectBuilder().add("method", "get")

.add("params", Json.createObjectBuilder()

.add("SelectionCriteria", Json.createObjectBuilder())

.add("FieldNames", Json.createArrayBuilder()

.add("Id").add("Names")));

jo = jb.build();

System.out.println(jo);

}

}

Генерирует следующие объекты:

{"var":[null]}

{"a":"b"}

{"obj":{"inside":[null]}}

{"obje":{"invar":[{"inset":null}]}}

{"method":"get","params":{"SelectionCriteria":{},"FieldNames":["Id","Names"]}}

Пускается так: java test.JsonObjGen любой аргумент

После того как выяснил как создаются Json объекты, попытался залезть в Яндекс Директ тестовые кампании, которые уже были созданы. Не получилось. Проверил то же, но с помощью curl (на всех Linux-ах есть) - работает! Стал тестировать и пришёл к выводу, что http:// требует своего Output/InputStream. а https:// - своего. Потратил сутки на выяснение всяких деталей, но сейчас уже всё по полочкам разобрано и понятно.

Вот исходный текст (TestOAuthYaToken.java):

package test;

import givme.direct.*;

import java.io.*;

import java.net.*;

import java.util.*;

import javax.json.*;

import javax.net.ssl.*;

/*

* Написание этой тестовой задачки было продиктовано необходимостью

* проверить JsonReader.readObject() и Json.writeObject(JsonObject)

* при работе через http/https соединение.

*/

/*

* Запуск: java test.TestOAuthYaToken http/https://json.server.some.where/goal,

* где - goal - цель на json сервере.

* Я пускал для проверки так:

* java test.TestOAuthYaToken http://jsonplaceholder.typicode.com/posts

* или

* java test.TestOAuthYaToken https://api-sandbox.direct.yandex.ru/json/v5/campaigns

*/

/*

* Программка является тестовой, для проверки OAuth.yandex.ru токена.

* Который получался для приложения Яндекс Директа собственного написания.

* Токен получается браузером, как описано в документации на Яндекс Директ.

* По URL: https://oauth.yandex.ru/authorize?response_type=token&client_id=Id,

* где Id - идентификатор приложения для Яндекс Директ.

* Вид токена приблизительно: AgAAAAA0Vx_wAAX....

*

* Если response_type=token поменять на response_type=code, то получать будете код.

* Он, в отличие от токена, который живёт некоторое время. каждый раз разный.

* Вид приблизительно: 9433211.

*/

public class TestOAuthYaToken

{

public static void main(String v[])

{

for(String s:v)

{

reqResp(s);

}

/*

if(v.length == 0)

{

reqResp("https://"+POST.Id+":"+POST.Pass+"@"+POST.oauthServer+"/"+POST.requestCode+POST.Id);

}

*/

}

static void reqResp(String dst)

{

Base64.Encoder be = Base64.getEncoder();

HttpURLConnection hc = null;

HttpsURLConnection hs = null;

InputStream is = null;

JsonObject jo = null;

JsonObjectBuilder jb = Json.createObjectBuilder();

JsonReader jr = null;

JsonWriter jw = null;

OutputStream os = null;

URL url = null;

URLConnection uc = null;

try

{

url = new URL(dst);

System.out.println(url);

uc = url.openConnection();

uc.setRequestProperty("Authorization",

"Bearer " + POST.AuthT0);

uc.addRequestProperty("Content-Type", "application/json;charset=utf-8");

uc.setDoInput(true);

uc.setDoOutput(true);

/*

* Почему пришлось здесь сделать этот if(uc instanceof ...)?

* Всё просто! Так как делалось для того, чтобы проверить

* (были проблемы с отсылкой данных), то пришлось делить по протоколам. Это тестовая, но вполне

* работоспособная задача.

* В чём была проблема? Проблема была в том, что при использовании

* http:// нужно использовать ИМЕННО OutputStream от HttpURLConnection,

* а в случае httpS:// нужно использовать HttpsURLConnection. Иначе

* не работает этот самый Output/Input Streams. Эту проблему и решал.

*/

/*

* А проверял сначала на http://jsonplaceholder.typicode.com/posts, а он

* имеет http://, а не https:// протокол. Можно и https://, но там ciphers

* не совпадают с набором и у моей Java и handshake failed.

*/

if(uc instanceof HttpURLConnection)

{

hc = (HttpURLConnection)uc;

hc.setRequestMethod("POST");

}

else

{

hs = (HttpsURLConnection)uc;

hs.setRequestMethod("POST");

}

jb = Json.createObjectBuilder().add("method", "get")

.add("params", Json.createObjectBuilder()

.add("SelectionCriteria", Json.createObjectBuilder())

.add("FieldNames", Json.createArrayBuilder()

.add("Id").add("Name").add("DailyBudget").add("ClientInfo")

.add("Statistics")

));

jo = jb.build();

/*

* После использования JsonObjectBuilder функции build()

* JsonObjectBuilder обнуляется! И если нужен, то надо собирать

* его по-новой! Или (проще!) сохранить результат в JsonObject.

*/

uc.connect();

if(uc instanceof HttpURLConnection)

os = hc.getOutputStream();

else

os = hs.getOutputStream();

jw = Json.createWriter(os);

jw.writeObject(jo);

System.out.println(jo);

/*

* Открытие InputStream только после открытия OutputStream.

*/

if(uc instanceof HttpURLConnection)

is = hc.getInputStream();

else

is = hs.getInputStream();

jr = Json.createReader(is);

jo = jr.readObject();

System.out.println(uc.getHeaderFields());

System.out.println(jo);

}

catch(Exception e)

{

e.printStackTrace();

}

}

}

Второй файл (POST.java):

package givme.direct;

public interface POST

{

public String method = "POST";

public String server = "api.direct.yandex.ru";

public String oauthServer = "oauth.yandex.ru";

public String sandboxServer = "api-sandbox.direct.yandex.ru";

public String contentType = "application/json;charset=utf-8";

public String Authorization = "Authorization: Bearer ";

public String ConTyJSON = "Content-Type: application/json;charset=utf-8";

public String JSON = "/json/v5/";

public String SOAP = "/v5/";

public String WSDL = "/v5/";

// Request Code/Token

public String requestCode = "authorize?response_type=code&client_id="; // client_id=Id

public String requestToken = "authorize?response_type=token&client_id="; // client_id=Id

// Token

public String tokenRequest = "https://oauth.yandex.ru/token";

public String tokenGrant = "grant_type=";

public String code = "code="; // code=Код Подтверждения из запроса токена

// https://oauth.yandex.ru/authorize?response_type=token&client_id=Id

public String clientId = "client_id="; // client_id=Id

public String clientSecret = "client_secret="; // client_secret=Pass

// Application

public String Id = "80e1......709bab";

public String Pass = "d9fa......e06f7";

// Мастер-токен.

public String Token = "ig6......J";

// Код подтвеждения. Каждый раз разный. Получается если

// в response_type ставить code: response_type=code.

public String Code0 = "9435595";

// Из URL по API. Это результат получения кода по URL выше.

// Получается одинаково с кодом, но response_type=token. Одинаков для логина и приложения.

// user шмузер имеет такой токен:

public String AuthT0 = "AgAAAAA0Vx_wAAX.........5o";

}

Эти два файла компилируются:

javac -d . POST.java TestOAuthYaToken.java

Потом запускаются:

java test.TestOAuthYaToken <json сервер>

В качестве серверов можно тестирования взять http://jsonplaceholder.typicode.com/posts

Для Яндекс Директ нужна тестовая Песочница:

https://api-sandbox.direct.yandex.ru/json/v5/campaigns

При общении с http://jsonplaceholder.typicode.com/posts выдаёт (часть вывода вырезана):

api-sandbox.direct.yandex.ru/json/v5/campaigns

http://jsonplaceholder.typicode.com/posts

{null=[HTTP/1.1 201 Created], CF-RAY=[50a6fff80fb2d729-FRA], Server=[cloudflare], X-Content-Type-Options=[nosniff], Connection=[keep-alive], Pragma=[n...............44:45 GMT; path=/; domain=.typicode.com; HttpOnly], Expires=[-1], Content-Length=[134], Location=[http://jsonplaceholder.typicode.com/posts/101], X-Powered-By=[Express], Content-Type=[application/json; charset=utf-8]}

{"method":"get","params":{"SelectionCriteria":{},"FieldNames":["Id","Names"]},"id":101}

При общении с тестовой Песочницей выдаёт (часть вывода опять вырезана):

https://api-sandbox.direct.yandex.ru/json/v5/campaigns

{"method":"get","params":{"SelectionCriteria":{},"FieldNames":["Id","Name","DailyBudget","ClientInfo","Statistics"]}}

{Transfer-Encoding=[chunked], null=[HTTP/1.1 200 OK], RequestId=[.......................................................Content-Security-Policy=[default-src 'none'], X-XSS-Protection=[1; mode=block], Content-Type=[application/json; charset=utf-8]}

{"result":{"Campaigns":[{"Statistics":{"Clicks":12...2,"Impressions":14...0},"ClientInfo":"..........","DailyBudget":null,"Id":33...6,"Name":"Test API Sandbox campaign 1"},{"ClientInfo":"..........","Statistics":{"Clicks":0,"Impressions":0},"Id":3...97,"Name":"Test API Sandbox campaign 2","DailyBudget":null},{"Id":3...98,"Name":"Test API Sandbox campaign 3","DailyBudget":null,"ClientInfo":"......","Statistics":{"Impressions":0,"Clicks":0}}]}}

Для чего я написал эту заметку и привёл исходники для Java? Всё просто - я не нашёл в Инете ни одного более-менее подробного исходника для Java реализации API Yandex Direct!!! Ни одного!!!

Даже для начала, для понимания как всё это нужно строить, и механизм работы нигде не объяснён. Потому взял на себя труд дать пройти кому-то другому этот этап легче. Теперь есть JSON, есть Http/HttpsURLConnection и видно как со всем этим работать.

P.S. Если у меня ещё возникнут подробные проблемы - вынесу сюда их решение. Ругайте, критикуйте - я не боюсь. Только учитывайте, что всё это тестовые куски, рабочие, но тем не менее - тестовые программы. Для того чтобы понять КАК работает Яндекс Директ API.