Русский
Русский
English
Статистика
Реклама

Curl

Перевод Обратная сторона Open Source-славы как угрожают автору curl

23.02.2021 14:15:47 | Автор: admin

Прим. перев.: уникальная история, что всколыхнула интернет в эти дни, показывает неожиданную сторону того, что могут заслужить авторы самых популярных Open Source-проектов. Ниже представлен перевод недавней заметки из блога шведского программиста Daniel Stenberg оригинального автора и главного разработчика curl, обладателя премии Polhem Prize (вручается в Швеции за выдающиеся инженерные достижения).

Как вам, наверное, известно, в своем блоге я публикую забавные электронные письма, которые периодически приходят мне на адрес. Ну, знаете, когда мне пишут о проблемах с каким-то своим устройством (вроде автомобиля), потому что мой email где-то там засветился.

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

Увы, не все эти письма забавны.

Категория: не смешно

Сегодня я получил следующее письмо:

От: Al Nocai <[скрыто]@icloud.com>

Дата: Пт, 19 Фев 2021 03:02:24 -0600

Тема: Я тебя убью

Да, именно такая тема (I will slaughter you).

Более 20 лет являясь мейнтейнером ПО с открытым исходным кодом, я прекрасно осведомлен о флейм-войнах и персональных выпадах. Благо, у меня довольно толстая кожа и словам нелегко меня уколоть. Минута ушла на то, чтобы все осмыслить и понять, что это прямая угроза физической расправой. И предназначается она именно мне. Откровенно говоря, к такому уровню агрессии я оказался не готов.

К письму были приложены семь изображений без каких-либо пояснений. Изображения выглядели как скриншоты с телефона, и на первом виднелся написанный мной код и строка с копирайтом:

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

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

Я решил, что имя в письме выдуманное, а адрес электронной почты, скорее всего, одноразовый. Часовой пояс в строке с датой намекал на Центральное стандартное время США, но, конечно, мог быть подделкой, как и все остальное.

Мой ответ

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

Это письмо совершенно и абсолютно неприемлемо, и оно глубоко ранило меня. Вам должно быть стыдно! Подумайте о том, чтобы пересмотреть свои манеры.

Я понятия не имею, что означают эти скриншоты. Вижу только, что кто-то где-то использует код, написанный мной. Этот код можно найти практически на каждом подключенном к интернету устройстве, и в большинстве случаев пользователи скачивают этот код и используют его совершенно бесплатно и не ставят меня в известность об этом.

Очевидно, что вы не достойны моего кода.

Впрочем, я не надеялся, что мой ответ будет прочитан или что-то изменит.

Примечание: Обновление ниже добавлено после первоначальной публикации.

Ответ Al Nocai

Вопреки моим ожиданиям, он ответил. Его ответ нет смысла комментировать, но я все же опубликую его здесь ради открытости.

Мне плевать. Твое дерьмовое ПО послужило вектором атаки, из-за которой я потерял многомиллионный оборонный проект.

Они использовали твое дерьмовое ПО, чтобы украсть root у меня и у многих других. Из-за этого только на прототипировании я потерял более 15 тысяч долларов.

С октября я живу в изоляции всему виной твое дерьмовое ПО, из-за которого долбаная Google-шпана может развести меня, пытаясь выманить из изоляции, поскольку они слишком бедны, чтобы представлять, что делают.

Знаешь, что я сделал, чтобы заслужить это? Я пытался разработать торговый путь в сфере технологий и разработать методики обучения на основе проектов, чтобы детки были при деле. И знаешь, кто во всех этих файлах? Ты. Это отвратительно. Меня взломали в октябре 2020, перехватив трафик федерального сервера, и всем этим я обязан тебе.

Мне пришлось сидеть и смотреть вот на это:

1. fireeye в октябре 2020;
2. Solarwinds в октябре 2020;
3. взлом модемов Zyxel в октябре 2020;
4. множество векторов атаки на Sigover с помощью XML injection;
5. стохастическая шаблонизация в JS с использованием выражений сравнения для записи в регистры данных;
6. 50-миллиардные компании охотятся за мной, поскольку я разоблачил их дерьмовое вредоносное ПО.

И меня взломали и перенаправили важную корреспонденцию в другое место. Какой-то больной ублюдок разрушил мою жизнь с помощью кода с твоим именем. Из-за этой ситуации я даже не могу уехать из страны; если уж ты смог запилить код, который гадит всем подряд, то мне совершенно наплевать на твои чувства по этому поводу.

Ты построил гоночный болид для Формулы-1, а ключи бросил шпане с раздутым эго. А теперь из-за всего этого мусора мне приходится разбираться с уязвимостями нулевого дня в Win10.

Я потерял свою семью, свою страну, своих друзей, свой дом и 6 лет работы, пытаясь построить лучшее будущее для потомков. И во всем этом виноват твой код. Этот код используется для того, чтобы похищать root и внедрять эксплойты. Этот код используется для шантажа.

Так что я ни капельки не расстроен. Ты прекрасно представлял себе возможности этого кода. И думал, что все это большая шутка. А я не смеюсь. Я уже давно прошел эту точку. /- Al

Al продолжает

Через девять часов после публикации оригинальной заметки в блоге, Al прислал мне еще два электронных письма. Вот они:

Письмо 3:

https://davidkrider.com/i-will-slaughter-you-daniel-haxx-se/

Валяй. Ты меня не испугаешь. Что привело меня сюда? 5-е покушение на мою жизнь. Условия предоставления услуг Apple? Иди к черту со своей платформой.

Забавно, что он нашел публикацию о заметке в моем блоге.

Письмо 4:

Есть проект: Анализ риска военных операций в урбанизированной местности с помощью широкополосного анализа электромагнитного спектра с использованием различных преобразований Фурье.Ты и этот David Krider, кем бы он ни был, вы часть всего этого.

- Взломы федеральных серверов;
- соучастие в покушение на убийство;
- мошенничество;
и т.д.

На данный момент я разговаривал с ФБР, региональным отделением ФБР, министерством по делам ветеранов США (VA), офисом генерального инспектора VA, Федеральной комиссией по связи (FCC), Комиссией по ценным бумагам (SEC), АНБ, Минздравом (DOH), Управлением служб общего назначения (GSA), МВД, ЦРУ, Бюро финансовой защиты потребителей (CFPB), Министерством жилищного строительства и городского развития (HUD), MS, Convercent; по состоянию на сегодня представители 22 отдельных ведомств вызванивают меня и тратят мое время.

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

(Прим. перев.: в английском варианте сохранено своеобразное форматирование и опечатки.)

К письму 4 также был приложен PDF-файл с названием BustyBabes 4.pdf. Он представляет собой 13-страничный документ о NERVEBUS NERVOUS SYSTEM. Первый параграф гласит: NerveBus Nervous System призвана быть универсальной платформой для всестороннего и комплексного анализа, предоставляя конечному пользователю целостную, связную и реальную информацию о среде, которую тот мониторит. В документе не упоминается ни curl, ни мое имя.

Поскольку я ничего не знаю о статусе этого документа, не буду делиться им публично, но скриншот первой страницы приложу:

Дополнительная информация

Тема на Hacker news и Reddit.

Я сообщил об угрозе в шведскую полицию (по месту жительства).

P.S. от переводчика

В то время, как в комментариях Hacker News обсуждают, что приведенный в письме список обвинений (где упомянуты FireEye, SolarWinds и т.п.) набор бессмысленных фраз (что, возможно, указывает на психические отклонения), в комментарии к публикации в блоге Daniel Krider можно найти некоторые уточнения по этому поводу от самого автора:

I have since October, had my entire life sandboxed by individual Daniel Patrick Ehrlich to find software loopholes to aid in development of SCNR, an aggregation bot for Open Source Intelligence tied to Subverse Media and Timothy Pool, the Journalist.

I know the following:
1. React JS primary utility is to obfuscate URIs to inject malicious code
2. This code is commonly down my user agents, KHTML, primarily Favicons as they are the SVG type, and carry injectable XML.
3. Qualcom BT Adapters and drivers are routinely hacked via the SigOver attack vector through malicious sub packet injection.

P.P.S.

Читайте также в нашем блоге:

Подробнее..

Перевод Пробелы бывают разные ampnbsp C2A0

18.06.2021 16:08:17 | Автор: admin
image

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

Эта простая на первый взгляд проблема бросала вызов всем моим попыткам ее объяснить. Я придумал множество замечательных теорий: проблемы с моими классами CSS или с полями и отступами. Несоответствующие теги HTML. Ошибки браузера. Я попробовал три разных браузера и во всех получил одинаковые результаты.

Чувствуя себя сбитым с толку, я снова посмотрел на два раздела HTML в редакторе WordPress (текстовое представление) и подтвердил, что они полностью идентичны. Затем я попробовал встроенные в Firefox инструменты веб-разработчика для просмотра отображаемых элементов страницы и сравнил все их свойства CSS. Идентичны, но каким-то образом визуализированы по-разному. Я использовал инструменты разработчика, чтобы проверить точный HTML, полученный с моего веб-сервера, снова проверил два раздела и убедился, что они символьно идентичны. Инструмент Firefox источник страницы также подтвердил, что эти два раздела полностью идентичны.

К этому моменту я был готов обвинить космические лучи или магию вуду. Я обнаружил, что каждый раз, когда я копирую любой похожий раздел HTML, только что вставленный раздел будет отображаться в браузере с неправильным интервалом между элементами. Как такое могло быть? Затем я попробовал W3C Validator, который обнаружил некоторые другие проблемы с моей страницей, но ничего не могло объяснить такое поведение. И снова он подтвердил, что, несмотря на разную визуализацию в браузере, два раздела HTML идентичны.

Ясно, что что-то не складывалось. Я использовал curl для загрузки веб-страницы со своего веб-сервера, просмотрел локальную копию и увидел то же поведение, что и раньше. Но когда я открыл сохраненный документ .html с помощью шестнадцатеричного редактора, я наконец получил ответ. Эти два раздела HTML не были идентичными: в одном разделе использовался другой тип пробела, чем в другом.

Что за черт.

Я обнаружил, что исходный раздел HTML содержит неразрывные пробелы. Но вместо того, чтобы кодировать их с помощью они были закодированы юникод-символами C2A0. Не знаю, когда и как это произошло, но виню в этом WordPress. При просмотре этого раздела в редакторе HTML WordPress пробелы C2A0 выглядели как обычные пробелы, и при копировании раздела внутри редактора неразрывные пробелы автоматически преобразовывались в нормальные пробелы с шестнадцатеричным значением 20. Таким образом, скопированная версия отображалась по-другому, хотя исходный HTML оказался таким же.

Это похоже на ремейк 0 О, только хуже. Я даже не знал, что неразрывные пробелы имеют свою кодировку в Юникоде я подумал, что был единственным способом их закодировать. Я снова изменил HTML, чтобы использовать и теперь все работает нормально.

Я удивлен, сколько разных инструментов не смогли выявить это тонкое, но важное различие между типами пробелов в исходном HTML-коде. Редактор HTML WordPress не смог показать или правильно обработать разницу. Сбой инструментов веб-разработчика Firefox и инструментов источника страниц. Ошибка исходного представления валидатора W3C. Curl плюс шестнадцатеричный редактор был единственным способом окончательно установить достоверную информацию о точном содержании исходного кода HTML.
Подробнее..

Перевод Что JavaScript-разработчику следует знать о Curl

28.06.2020 14:08:19 | Автор: admin


Доброго времени суток, друзья!

Представляю Вашему вниманию перевод статьи What JavaScript Developers Should Know About Curl автора Valery Karpov.

Curl это популярный инструмент командной строки, часто используемый для отправки HTTP-запросов. Curl поддерживает большое количество протоколов, однако как Node.js-разработчик вы, скорее всего, будете использовать его для отпраки http-запросов к RESTful API.

Документация curl представляет собой перечень из 383 флагов командной строки, поэтому в ней очень трудно найти то, что ищешь. В этой статье я хочу поделиться с вами некоторыми часто используемыми мной шаблонами. Для примеров будет использоваться сервис httpbin.org.

Отправка http-запроса


Для начала убедитесь в том, что у вас установлен curl, выполнив команду curl --version.

$ curl --versioncurl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3Release-Date: 2018-01-24Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL 

Для отправки запроса необходимо запустить curl url. Например, для отправки GET-запроса к https://httpbin.org/get?answer=42 следует запустить curl https://httpbin.org/get?answer=42.

$ curl https://httpbin.org/get?answer=42{  "args": {    "answer": "42"  },   "headers": {    "Accept": "*/*",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8d737-b39c6a466892725bbb52b916"  },   "origin": "69.84.111.39",   "url": "https://httpbin.org/get?answer=42"}

После успешного завершения запроса curl возвращает тело http-ответа. Для того, чтобы заставить curl вернуть весь ответ, включая заголовки, используйте флаг -i.

$ curl -i https://httpbin.org/get?answer=42HTTP/2 200 date: Tue, 16 Jun 2020 14:30:57 GMTcontent-type: application/jsoncontent-length: 801server: istio-envoyaccess-control-allow-origin: *access-control-allow-credentials: truex-envoy-upstream-service-time: 2{  "args": {    "answer": "42"  },   "headers": {    "Accept": "*/*",     "Content-Length": "0",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8d7a1-cb3954c09299eb9e0dff70a6",     "X-B3-Parentspanid": "dffc55451e64b5fc",     "X-B3-Sampled": "0",     "X-B3-Spanid": "8e233a863fb18b6c",     "X-B3-Traceid": "45bd12a9067fb5c0dffc55451e64b5fc",     "X-Envoy-External-Address": "10.100.91.201",     "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/httpbin-istio/sa/httpbin;Hash=c1ff14671b3e24ee794f9a486570abf8ccc9d622846611d3f91a322db4d480cd;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"  },   "origin": "69.84.111.39,10.100.91.201",   "url": "http://httpbin.org/get?answer=42"}

Это полный http-ответ. Заголовками ответа являются строки от date: до x-envoy-upstream-service-time:.

Загрузка файлов


Wget самый распространенный инструмент для загрузки файлов посредством командной строки. Он входит в комплект большинства диструбутивов Linux. Однако в OSX его нет.

Команда wget url аналогична команде curl -OL url. Опция это опция --remote-name, которая говорит curl сохранить тело ответа в локальном файле. Опция -L говорит curl следовать перенаправлениям.

Например, ниже представлено изображение с Unsplash, его URL https://images.unsplash.com/photo-1506812574058-fc75fa93fead.



Для загрузки этого изобюражения необходимо выполнить следующее:

$ curl -OL https://images.unsplash.com/photo-1506812574058-fc75fa93fead  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100 12.1M  100 12.1M    0     0  3927k      0  0:00:03  0:00:03 --:--:-- 3927k

Опция -O говорит curl использовать строку после последнего / в качестве имени файла. В приведенном примере изображение будет сохранено в текущей директории с именем photo-1506812574058-fc75fa93fead. Для определения имени файла используйте опцию (строчная буква о).

$ curl -o miami-beach.jpg https://images.unsplash.com/photo-1506812574058-fc75fa93fead  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100 12.1M  100 12.1M    0     0  6083k      0  0:00:02  0:00:02 --:--:-- 6083k$ ls -l miami-beach-jpg-rw-rw-r-- 1 val val 12788445 Jun 16 11:03 miami-beach.jpg

Отправка авторизованного запроса


Заголовок авторизации используется для включения в запрос данных для авторизации при обращении к RESTful API. Для добавления указанных данных необходимо использовать флаг -H. Например, если ваш ключ интерфейса (API key) my-secret-token, вы можете включить его в http-запрос следующим образом:

$ curl -H "Authorization: my-secret-token" https://httpbin.org/get{  "args": {},   "headers": {    "Accept": "*/*",     "Authorization": "my-secret-token",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8e1a5-a3aa30e0765a7980b04ca4a0"  },   "origin": "69.84.111.39",   "url": "https://httpbin.org/get"}

Обратите внимание, что httpbin.org возвращает заголовки http-запроса в теле ответа в свойстве headers.

Curl также поддерживает авторизацию по-умолчанию посредством флага -u. В следующем примере мы отправляем запрос с именем пользователя user и паролем pass:

$ curl -i -u "user:pass" https://httpbin.org/basic-auth/user/passHTTP/2 200 date: Tue, 16 Jun 2020 15:18:45 GMTcontent-type: application/jsoncontent-length: 47server: istio-envoyaccess-control-allow-origin: *access-control-allow-credentials: truex-envoy-upstream-service-time: 1{  "authenticated": true,   "user": "user"}

Вот что происходит при отправке неправильного имени пользователя или пароля:

$ curl -i -u "user:wrongpass" https://httpbin.org/basic-auth/user/passHTTP/2 401 date: Tue, 16 Jun 2020 15:18:51 GMTcontent-length: 0server: istio-envoywww-authenticate: Basic realm="Fake Realm"access-control-allow-origin: *access-control-allow-credentials: truex-envoy-upstream-service-time: 12

Отправка POST-запроса, содержащего JSON


Флаг -X говорит curl, какой метод следует использовать: PUT, POST и т.д. По-умолчанию curl использует метод GET, поэтому писать curl -X GET не нужно.

Флаг -X часто используется совместно с флагом -d, позволяющим добавить тело запроса. В следующем примере показано как отправить POST-запрос, содержащий некоторый json:

$ curl -X POST -d '{"answer":42}' https://httpbin.org/post{  "args": {},   "data": "",   "files": {},   "form": {    "{\"answer\":42}": ""  },   "headers": {    "Accept": "*/*",     "Content-Length": "13",     "Content-Type": "application/x-www-form-urlencoded",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8e3fd-8437029087be44707bd15320",     "X-B3-Parentspanid": "2a739cfc42d28236",     "X-B3-Sampled": "0",     "X-B3-Spanid": "8bdf030613bb9c8d",     "X-B3-Traceid": "75d84f317abad5232a739cfc42d28236",     "X-Envoy-External-Address": "10.100.91.201",     "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/httpbin-istio/sa/httpbin;Hash=ea8c3d70befa0d73aa0f07fdb74ec4700d42a72889a04630741193548f1a7ae1;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"  },   "json": null,   "origin": "69.84.111.39,10.100.91.201",   "url": "http://httpbin.org/post"}

Обратите внимание, что по-умолчанию значением заголовка Content-Type является application/x-www-form-urlencoded. Для json это является неверным, поэтому для определения Content-Type следует использовать флаг -H:

$ curl -X POST -d '{"answer":42}' -H "Content-Type: application/json" https://httpbin.org/post{  "args": {},   "data": "{\"answer\":42}",   "files": {},   "form": {},   "headers": {    "Accept": "*/*",     "Content-Length": "13",     "Content-Type": "application/json",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8e45e-ad875af4f83efd4379b86c34",     "X-B3-Parentspanid": "5f4f33d1c5ea13aa",     "X-B3-Sampled": "0",     "X-B3-Spanid": "a062c9bf2ebfd4bd",     "X-B3-Traceid": "44aa8d62412ae34d5f4f33d1c5ea13aa",     "X-Envoy-External-Address": "10.100.86.47",     "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/httpbin-istio/sa/httpbin;Hash=2f0b3331fe4d512975b4b82583a55dd5d1196023d0dfce9e0abed246991c5b67;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"  },   "json": {    "answer": 42  },   "origin": "69.84.111.39,10.100.86.47",   "url": "http://httpbin.org/post"}

Отправка PUT-запроса, содержащего JSON-файл


Флаг -d также поддерживает отправку данных из локальных файлов.

Например, представим, что у нас есть файл data.js, содержащий такие данные:

{"answer": 42}

Для отправки PUT-запроса с этим файлом в качестве тела запроса вы можете присвоить флагу -d значение @data.json. Префикс @ говорит curl загрузить тело запроса из файла data.json:

$ curl -X PUT -d '@data.json' -H "Content-Type: application/json" https://httpbin.org/put{  "args": {},   "data": "{\"answer\":42}",   "files": {},   "form": {},   "headers": {    "Accept": "*/*",     "Content-Length": "13",     "Content-Type": "application/json",     "Host": "httpbin.org",     "User-Agent": "curl/7.58.0",     "X-Amzn-Trace-Id": "Root=1-5ee8e745-37c4ef06326b7b4354a16b94",     "X-B3-Parentspanid": "a4f8f91f4f1b051e",     "X-B3-Sampled": "0",     "X-B3-Spanid": "a018b1a3fcebdc68",     "X-B3-Traceid": "7b48b01dc3f632eea4f8f91f4f1b051e",     "X-Envoy-External-Address": "10.100.91.201",     "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/httpbin-istio/sa/httpbin;Hash=6035260d9d551af6c1907270653214e8d3195abbdd19078c1c84fd9a4106f260;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"  },   "json": {    "answer": 42  },   "origin": "69.84.111.39,10.100.91.201",   "url": "http://httpbin.org/put"}

Заключение


Резюмируя, вот опции curl, которые я нахожу самыми полезными:

  • -X определяет метод запроса, например, curl -X POST url
  • -d определяет тело запроса в виде строки в PUT и POST-запросах. Используйте @ для извлечения данных из файла
  • -H определяет заголовок запроса, например, curl -H "Authorization: my-secret-token" url
  • -u аутентификационные данные для стандартной авторизации
  • -O сохраняет тело запроса в файл
  • -i показывает полный ответ, включая заголовки

Curl полезный инструмент взаимодействия с API посредством командной строки, независимо от того, сторонний это API или API, который вы разрабатываете. Для быстрого тестирования curl подходит лучше, чем Axios в Node.js или настройка запроса в Postman, если вы знакомы с их синтаксисом.

Благодарю за потраченное время. Надеюсь, оно было потрачено не зря.
Подробнее..

Сборник полезных ссылок для системного администратора

14.08.2020 14:14:13 | Автор: admin


Сборник полезных ссылок для системного администратора, руководства, инструкции, учебные курсы, секреты-советы и многое другое ниже в нашем еженедельном посте.

Когда вы системный администратор, то отвечаете не за что-то одно, а сразу на всё. Причем горящие задачи частенько возникают самым внезапным образом. Если не заниматься чем-то каждый день, оно рано или поздно вылетает из головы, и вы просто не можете вспомнить нужные команды и опции именно в том момент, когда кругом всё горит и рушится. Поэтому мы подготовили 10 полезных шпаргалок, которые пригодятся вне зависимости от того, насколько вы крутой сисадмин:

  • Сети
    PDF-шпаргалка по утилитам и командам Linux для управления серверами и сетями.
  • Файрвол
    Эта шпаргалка поможет прокачать знания по безопасности Linux.
  • Продвинутый SSH
    Для большинства SSH это просто инструмент для удаленного входа. А на самом деле он может гораздо больше.
  • Пользователи и разрешения
    Здесь собраны примеры и команды, которые пригодятся при управлении пользователями и разрешениями.
  • Базовые команды
    Linux-команды для рутинных задач: навигация и управление файлами, установка софта на типовых дистрибутивах, сервисы.
  • Git
    Сегодня это де-факто стандарт управления версиями, пора научиться работать с ним эффективно.
  • Curl
    Типовые синтаксисы и сценарии применения утилиты curl, в том числе, как использовать ее для запроса API.
  • SELinux
    Полезное руководство по использованию и работе с Security-Enhanced Linux.
  • Kubectl
    9 важных команд kubectl для устранения неполадок и управления кластерами Kubernetes.
  • awk
    Памятка по типовым функциями awk.

Бонус: Bash-сценарии руководство для сисадминов



Эта электронная книга расскажет, почему shell-сценарии это лучший способ автоматизации задач, и научит создавать простые сценарии.

Почтовая рассылка Enable Sysadmin

Руководства, инструкции, учебные курсы, секреты-советы и многое другое.
Подробнее..

Конвертация скрипта Bash в код С для отправки СМС через usb модем HUAWEI E3372

17.06.2020 16:18:48 | Автор: admin
Несколько моих проектов отправляют СМС и в последнее время после обновлений сервера, а может и ряда других причин, отправка СМС стала почти невозможной.

Старенький USB модем HUAWEI (марку не буду разглашать) перестал стабильно висеть на одном COM порту и временами переподключался на другие порты, совсем отключался и терял антенну.

Да и ситуация с библиотекой GSMComm была непонятной и болезненной.

GSMComm это пакет для телефонов GSM, в основном для выполнения задач, связанных с SMS.
www.nuget.org/packages/GSMComm последня версия 1.21.1 от 10.10.2015 года.


Поиск по интерент показал, что есть возможность использовать встроенный функционал WEB API новых модемов HUAWEI, более эффективно, чем старый подход с AT командами реализованный в GSMComm.

Выяснилось, что есть прекрасный usb модем HUAWEI E3372, который почти хакерским способом способен отправлять СМС как из скрипта (Curl + Bash), так и из кода (Python, Perl), и, как я предположил, из C#.

Самое печальное, что компания HUAWEI не предоставляет никакой документации как это сделать и все найденные методы имели экспериментальный харктер и зависели от семейства устройств.

В общем, опираясь на найденный материал, не гарантирующий работу кода с момедом, был приобретен HUAWEI E3372.

Не углублясь в эксперименты с Python или Perl я решил попробовать разобраться с вариантами Bash + Curl.

В общем, после нескольких экспериментов был найден работающий код под MS Windows 10 + Git Bash for MS Windows.

Скрипт

curl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1#TOKEN=$(curl -s -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)TOKEN=$(echo $TOKEN | cut -d'"' -f 10)echo $TOKEN > token.txtNUMBER=$1MESSAGE=$2LENGTH=${#MESSAGE}TIME=$(date +"%Y-%m-%d %T")TOKEN=$(<token.txt)SMS="<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>$NUMBER</Phone></Phones><Sca/><Content>$MESSAGE</Content><Length>$LENGTH</Length><Reserved>1</Reserved><Date>$TIME</Date></request>"echo $SMScurl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS" http://192.168.8.1/api/sms/send-sms --header "__RequestVerificationToken: $TOKEN" --header "Content-Type:text/xml" --header "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" --header "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3"


Скрипт удивительным образом работал. И это было уже счастье, так как деньги были потрачены на модем не зря!

Осталось только понять КАК же он работает. Почитав документацию по Curl и Bash (ну по bash я не читал так догадался) прояснилась работа скрипта. Привожу этот же скрипт с моими комментариями.

Скрипт с комментариями

# https://stackoverflow.com/questions/28070500/grab-current-sessions-cookie-with-curl/28070870# Содержимое файла session.txt определяется опцией -b# Сделать GET запрос и получить куки в первый раз и записать их в файлcurl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1# Сделать GET запрос и получить куки во второй раз и записать в файл и сохранить содержимое страницы HTML в переменную TOKEN как строкуTOKEN=$(curl -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)# Извлчеь из переменной значение метатега из <meta name="csrf_token" content="b/XNeODpHCthQXEOEjBNkICn2n7e9v4e"/> и перезаписать в ту же переменную TOKENTOKEN=$(echo $TOKEN | cut -d'"' -f 10)# Отобразить на экранеecho "$TOKEN"# сохранить подстроку в файлеecho $TOKEN > token.txt# Получить два параметра командной строки: (1) номер телефона и (2) текст СМСNUMBER=$1MESSAGE=$2# Получить количество символов в текстеLENGTH=${#MESSAGE}# Получить текущее время и отформатировать егоTIME=$(date +"%Y-%m-%d %T")# Загрузить содержимое файла в переменнуюTOKEN=$(<token.txt)# Сфоромировать текст для отправки СМС как XML SMS="<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>$NUMBER</Phone></Phones><Sca/><Content>$MESSAGE</Content><Length>$LENGTH</Length><Reserved>1</Reserved><Date>$TIME</Date></request>"# Отобразить переменную с текстом СМС на экране echo $SMS# Сделать POST для отправки СМСcurl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS" http://192.168.8.1/api/sms/send-sms --header "__RequestVerificationToken: $TOKEN" --header "Content-Type:text/xml"

Скажу честно, что СМС на русском я не смог добиться. Приходит абракадабра. Так что этот вопрос остался открытым и если
у кого-то есть желание закрыть тему отправки СМС на русском милости просим, дерзайте. )))

Понимание работы скрипта принесло свою пользу и приблизило к написанию кода на C#.

Было понятно, что в нем должно быть также 3 запроса и должны они делать то же самое что и благословенный Curl. Поэтому в коде приведен Curl, а ниже, аналогичный ему C# код.

Код С# для WinForms

private void button1_Click(object sender, EventArgs e){   var ip = "192.168.8.1"; // IP адрес который выдает модем в браузере после установки   var phone = "+70000000000"; // Номер телефона   var msg = "Привет!!! СМС работает!!!";   var result = SendSMS(ip, phone, msg);   if (result)   {       //TODO  Сохранить в БД, например   }   else   {       //TODO  Сохранить в БД, например   }}private bool SendSMS(string ip, string phone, string msg){    try    {        /* curl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1 */        Cookie firstCookie = null;        Cookie secondCookie = null;        string token = string.Empty;        //В первый раз получить куки        var cookieContainer = new CookieContainer();        var uri = new Uri($"http://{ip}/html/index.html");        using (var httpClientHandler = new HttpClientHandler { CookieContainer = cookieContainer })        {            using (var httpClient = new HttpClient(httpClientHandler))            {                httpClient.GetAsync(uri).Wait();                var all = cookieContainer.GetCookies(uri);                firstCookie = all[0];            }        }        /*        TOKEN=$(curl -s -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)        TOKEN=$(echo $TOKEN | cut -d'"' -f 10)        echo $TOKEN > token.txt         */    // И спользуя куки из первого запроса получить страницу и извлечь из нее токен        if (firstCookie != null)        {            var cookieContainer2 = new CookieContainer();            cookieContainer2.Add(firstCookie); // Поместить в конейнер куки из первого запроса к сайту            var uri2 = new Uri($"http://{ip}/html/smsinbox.html");            using (var httpClientHandler = new HttpClientHandler            {                CookieContainer = cookieContainer2            })            {                using (var httpClient = new HttpClient(httpClientHandler))                {                    var html = httpClient.GetStringAsync(uri2).Result; // Получить страницу HTML                    var all = cookieContainer2.GetCookies(uri2);                    secondCookie = all[0];                    var doc = new HtmlAgilityPack.HtmlDocument(); // Используем HtmlAgilityPack чтобы преобразовать текст HTML в структурный вид                     doc.LoadHtml(html);                    var items = doc.DocumentNode.SelectNodes("//meta");                    if (items.Count >= 2) // Получить второй по счету meta тег.                    {                        token = items[1].GetAttributeValue("content", ""); // Получить значение метатега. Не спрашивайтепочему второй метатаг с токеном рабочий - не знаю )))                    }                }            }            // Когда в наличии есть куки и токен делаем отправку СМС через запрос POST            if (!string.IsNullOrEmpty(token))            {                var msgLength = msg.Length;                var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //    TIME=$(date +"%Y-%m-%d %T")                var sms = $"<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>{phone}</Phone></Phones><Sca/><Content>{msg}</Content><Length>{msgLength}</Length><Reserved>1</Reserved><Date>{time}</Date></request>";                /*# Сделать POST для отправки СМС                  curl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS"                     http://192.168.8.1/api/sms/send-sms  --header "__RequestVerificationToken: $TOKEN"  --header "Content-Type:text/xml"                 */                var uri3 = new Uri($"http://{ip}/api/sms/send-sms");                var client = new RestSharp.RestClient { BaseUrl = uri3 }; // Используем RestSharp для запроса (дело вкуса)                var request = new RestSharp.RestRequest(RestSharp.Method.POST);                // Формируем свой заголовой запроса - ничего лишненго все по примеру из Curl    request.AddHeader("__RequestVerificationToken", token);                var ses = secondCookie.ToString();                request.AddCookie("cookie", ses);                request.AddHeader("Content-Type", "text/xml");                request.AddHeader("X-Requested-With", "XMLHttpRequest");                request.AddParameter("text/html", sms, RestSharp.ParameterType.RequestBody);                RestSharp.IRestResponse response = client.Execute(request);                if (response.IsSuccessful)                {                    var xmlDoc = new XmlDocument();                    xmlDoc.LoadXml(response.Content); // <?xml version="1.0" encoding="UTF-8"?><response> OK </response>                    var responseElemenets = xmlDoc.GetElementsByTagName("response");                    var resultOK = responseElemenets[0].InnerXml.ToLower();                    return resultOK == "ok"; // Ну вот и признак того, что СМС отправлено, но без отчета о доставке.                 }            }        }    }    catch (Exception)    {        //TODO в лог ошибку;    }    return false;}


Как только у вас в руках рабочий C# код вы всегда можете его улучшить.

В моем случае он работает как часы и для количества СМС в минуту вполне годится. )))

Надеюсь эта статься принелса пользу и в профессиональном и экономическом смыслах.

Душевно благодарю!
Подробнее..

Из песочницы Сравнение разных django filter на примере демо базы PostgreSQL

09.07.2020 02:04:13 | Автор: admin

Вместо предисловия


Началось всё с того, что мне предложили в рамках предмета "Основы веб-программирования" поучаствовать в проекте, вместо проделывания лабораторных работ и курсовой, поскольку я заявил о том, что хотел быть делать нечто отдалённое от общего курса (и так уже достаточно знаний было по связке DRF + Vue, хотелось чего-то нового). И вот в одном из своих PR на github я решил использовать полнотекстовый поиск (задание намекало на это) для фильтрации контента, что заставило меня обратиться к документации Django в поисках того, каким же образом лучше это дело реализовать. Думаю, вы знаете большую часть из тех методов, что были там предложены (contains, icontains, trigram_similar). Все они подходят для каких-то конкретных задач, но не слишком хороши в, именно, полнотекстовом поиске. Пролистав чуть ниже, я наткнулся на раздел, в котором говорилось о взаимодействии Django и Pgsql для реализации document-based поиска, что меня привлекло, поскольку в постгре встроен инструмент для реализации этого самого [полнотекстового] поиска. И я решил, что скорее всего, django просто предоставляет API к этому поиску, исходя из чего такое решение должно работать и точнее и быстрее, чем любые другие варианты. Преподаватель мне не слишком поверил, мы с ним поспорили, и он предложил провести исследование на эту тему. И вот я здесь.


Начало работы


Первая проблема, которая передо мной встала поиск мокапа БД, чтобы не придумать каких-нибудь непонятных штук самому и я отправился гуглить и читать вики постгреса. В итоге остановился на их демо базе о полётах по России.


Хорошо, база найдена. Теперь нужно определиться в том, какие способы фильтрации будут использоваться для сравнения. Первое, что я бы хотел использовать, разумеется, стандартный метод search из django.contrib.postgres.search. Второе contains (ищет слово в строке) и icontains (предоставляет данные, игнорируя акценты, например: по запросу "Helen" будет результат: <Author: Helen Mirren>, <Author: Helena Bonham Carter>, <Author: Hlne Joy>), которые предоставляет сам django. Все эти способы фильтрации я так же хочу сравнить со встроенным поиском внутри postgresql. Искать я решил по таблице tickets в версии small она содержит 366733 записей. Поиск будет происходить по полю passenger_name, где, как нетрудно догадаться, содержится имя пассажира. Написано оно транслитом.


Дать django возможность работать с уже существующей БД


Вторая проблема разрешить django только чтение данных из нашей демонстрационной БД. Покопавшись ещё в документации django я нашёл каким же образом, можно составить модельки по существующей БД, чтобы не перепечатывать ручками всё:


$ python manage.py inspectdb > models.py

При этом, разумеется, сама БД должна быть обозначена в settings.py. Всего пару ошибочек мне пришлось поправить и всё заработало как следует. Сразу после этого я решил написать простенькую вьюшку, которая сможет нам эти данные вернуть. Браузер, разумеется очень напрягся (что и не мудрено), когда я пытался открыть адрес, по которому должно было вернуться 300к+ записей, поэтому я ограничил их число для 10, чтобы удостовериться, что они там хотя бы есть. А вообще, совершенно точно понятно, что запрос лучше отправлять через curl. Это явно скушает в разы меньше оперативной памяти.


Выбор метрик


Изначально я подумал, что считать время фильтрации в питоне получится, используя таймер для получения времени исполнения скрипта, и дополнительной метрикой должно было стать время исполнения запроса через curl, поскольку это показывает приблизительное время, за которое отфильтрованные данные дойдут до конечного пользователя. Кроме этого, следует сравнивать это время с эталонным (прямым исполнением соответствующих запросов в БД).


Фильтруем в django


Но поскольку я уже снял 600 измерений времени выполнения скрипта в таблице финальной решил оставить, просто чтобы было сразу понятно, что это время вообще мало что реально отражает.


Итоговая view для contains
class TicketListView(g.ListAPIView):    serializer_class = TicketSerializer    def get_queryset(self):                queryset = ''        params = self.request.query_params        name = params.get('name', None)        if name:            start_time = d.datetime.now()            queryset = queryset.filter(passenger_name__contains=name)            end_time = d.datetime.now()            time_diff = (end_time - start_time)            execution_time = time_diff.total_seconds() * 1000            print("Filter execution time {} ms".format(execution_time))        return queryset

Contains


Начнём с contains, по сути, он работает как WHERE LIKE.

Запрос в Django ORM/Запрос в sql для contains
queryset = queryset.filter(passenger_name__contains=name)

SELECT "tickets"."ticket_no", "tickets"."book_ref", "tickets"."passenger_id", "tickets"."passenger_name", "tickets"."contact_data" FROM "tickets" WHERE "tickets"."passenger_name"::text LIKE %IVAN%

Для того, чтобы получить результат из curl я выполнял запрос следующим образом (считается в секундах):


$ curl -w "%{time_total}\n" -o /dev/null -s http://127.0.0.1:8000/api/tickets/?name=IVAN1,242888

Свёл всё в таблице, на соответствующем листе.


Но если резюмировать отклонение от скорости фильтрации внутри самого постгреса достаточно большое, и по факту исполнение такого запроса к серверу займёт от 140 до 1400 мс. Не претендую на истину, но работает всё приблизительно так. А время самой фильтрации через ORM займёт от 73 до 600 мс, в то время как такая же фильтрация внутри постгреса выполняется за промежуток от 55 до 100 мс.


Icontains


Icontains работает несколько по-другому (он приводит всё к одному виду, чтобы сравнение было более близким). Код для вьюшки использовался почти аналогичный, только вместо contains icontains. Вот и вся разница.


Запрос в Django ORM/Запрос в sql для icontains
queryset = queryset.filter(passenger_name__icontains=name)

SELECT "tickets"."ticket_no", "tickets"."book_ref", "tickets"."passenger_id", "tickets"."passenger_name", "tickets"."contact_data" FROM "tickets" WHERE UPPER("tickets"."passenger_name"::text) LIKE UPPER(%IVAN%)

По итогу, отклонение в данном случае уже меньше, поскольку и сам постгрес начал тратить намного большее времени на исполнение запроса (порядка 300 мс), а исполнение такого запроса к серверу займёт у клиента от 200 до 1500 мс. Фильтрация через ORM от до 200 до 700 мс.


Full text search (через django.contrib.postgres)


Поскольку индексов никаких создано не было, full text search довольно сильно и вполне ощутимо проигрывает прошлым вариантам. Время исполнения запроса в постгресе колеблется около 1300 мс, а запрос к серверу занимает от 1000 до 1700 мс. При этом, фильтрация через ORM укладывается в промежуток от 1000 до 1450 мс.


Код
class TicketListView(g.ListAPIView):    serializer_class = TicketSerializer    def get_queryset(self):        # queryset = Tickets.objects.all()        queryset = ''        params = self.request.query_params        name = params.get('name', None)        if name:            start_time = d.datetime.now()            queryset = Tickets.objects.filter(passenger_name__search=name)            end_time = d.datetime.now()            time_diff = (end_time - start_time)            execution_time = time_diff.total_seconds() * 1000            print("Filter execution time {} ms".format(execution_time))            f = open('results.txt', 'a')            f.write('{}'.format(execution_time))            f.write('\n')            f.close()        return queryset

Full text search (через rest_framework.filters, точнее SearchFilter)


Если не использвоать именно FTS, то результаты получаются сравнимыми с FTS внутри постгре, но хуже, чем contains и icontains. От 200 до 1710 мс.


А с использованием FTS эффективность повышается, отклонение сводится к минимальному. В среднем, это займёт от 800 до 1120 мс.


Код
...from rest_framework import filters as fclass TicketListView(g.ListAPIView):    queryset = Tickets.objects.all()    serializer_class = TicketSerializer    filter_backends = [f.SearchFilter]    search_fields = ['@passenger_name']

Использование фильтров через модуль django-filter


Результаты почти совпали со стандартными contains и icontains, поэтому смысла детально это рассматривать не вижу. Да и в целом, модуль django-filter не показал какого-то ощутимого преимущества перед стандартными средствами фильтрации Django ORM.


Так что в итоге?


Если у вас есть большой объём данных нужно прописывать нормальные индексы и использовать полнотекстовый поиск (разумеется, только в том случае, когда соответствует вашим целям) с радостью и счастьем, потому что он решает довольно широкий круг проблем. Но вот всегда ли в нём есть необходимость уже решать вам. Я усвоил для себя, что в некоторых случаях (когда не стоит задачи именно полнотекстового поиска, а есть поиск по подстроке, который реализуется с помощью contains/icontains) лучше вовсе не использовать полнотекстовый поиск, потому что индексы в определённый момент начинают кушать всё больше и больше памяти вашего сервера, что, скорее всего, негативно скажется на работе вашего сервера.


В целом, моё понимание некоторых внутренних механизмов работы django благодаря этому исследованию устаканилось. И пришло, наконец, осознание разницы между поиском по подстроке и полнотекстовым поиском. Разнице в их реализации через Django ORM.

Подробнее..

8 Kubernetes-инсайтов, шпаргалка по Curl и онлайн-курс Разработка облачных приложений с микросервисными архитектурами

28.01.2021 20:21:55 | Автор: admin

Мы приготовили для вас много новых инсайтов, записей важных вебинаров, книжек и шпаргалок. Прокачивайте скилы, читайте, смотрите, думайте, применяйте на практике! Станьте частью DevNation!

Начни новое:

Скачать:

  • Шпаргалка по команде Curl
    Примеры использования и синтаксис curl, включая ее использование для запроса API.

  • Шпаргалка по базовым вещам Podman

  • Debezium на OpenShift
    Debezium это распределенная опенсорсная платформа для отслеживания изменений в данных. Благодаря ее надежности и скорости ваши приложения смогут реагировать быстрее и никогда не пропустят события, даже если что-то пойдет на так. Наша шпаргалка поможет с развертыванием, созданием, запуском и обновление DebeziumConnector на OpenShift.
    Загрузить шпаргалку

Чем заняться на досуге:

Мероприятия:

  • 28 января, DevNation: The Show
    Еженедельный часовой чат в прямом эфире. Как обычно, в программе свежие новости и интерактивная игра для участников.

  • DevNation Deep Dive: Kubernetes
    Поспешите, поезд Kubernetes уже отправляется узнайте, как применять, развертывать и использовать Kubernetes для решения задач, с которые вы сталкиваетесь в облаке .

Смотри в записи:

  • Вебинар DevNation Tech Talk Сборка kubectl-плагина с помощью Quarkus
    Разбираем, как с нуля спроектировать kubectl-плагин и собрать его, используя Quarkus. Также рассмотрим удобную работу с Kubernetes-кластером с использованием нативной компиляции для получения сверхбыстрых бинарников и расширений для Kubernetes-клиента.

  • jconf.dev
    Бесплатная виртуальная Java-конференция прямо у вас на экране: четыре техно-трека с нашими комьюнити-экспертами по Java и облаку, 28 углубленных сессий и два потрясающих основных доклада.

  • AnsibleFest
    Два дня интереснейших докладов, демонстраций и практических занятий. Отличная возможность узнать, как разработчики, администраторы и ЛПР в сфере ИТ отвечают на вызовы перемен с помощью гибких технологий автоматизации с открытым кодом, которые позволяют перейти от того, что есть, к тому, что нужно.

  • J4K Conference
    Новая виртуальная конференция по Kubernetes, Java и облаку: 17 сессий с сотрудниками Red Hat, включая доклад Марка Литтла (Mark Little), главного человека в Red Hat по связующему ПО.

По-русски:

Подробнее..

Перевод Автоматизированное тестирование баз данных в Java с помощью JdbcTemplate

15.03.2021 14:07:31 | Автор: admin

В преддверии старта курса "Java QA Automation Engineer" подготовили перевод полезного материала.

Также приглашаем поучаствовать в открытом вебинаре на тему HTTP. Postman, Newman, Fiddler (Charles), curl, SOAP. SoapUI. На этом занятии участники вместе с экспертом разберут, какие бываю API и каким способом можно проверить, что backend отдает ожидаемые данные, а также познакомятся с основными инструментами для тестирования.


Бывает, и достаточно часто, что во время автоматизированного тестирования наши тесты должны взаимодействовать с базами данных. Иногда нам нужно установить какие-либо тестовые данные. В других случаях нам нужно совершать запросы в базу данных, чтобы получить те самые тестовые данные. И давайте не будем забывать об очистке данных, которые мы использовали и которые больше нам не нужны. В этой статье я покажу, как вы можете использовать класс Spring JdbcTemplate для упрощения работы с базой данных MySQL из ваших автоматизированных тестов на Java.

Вам также следует взглянуть на это замечательное дополнение к базе данных MySQL, созданное сообществом для библиотеки дополнений TestProject, позволяющее расширить возможности тестирования с помощью предварительно созданных автоматизированных экшенов, которые вы можете мгновенно внедрить в ваши написанные и закодированные тесты.

Требования

Прежде чем мы сможем начать наши взаимодействия с базой данных, нам нужно кое-что настроить. А именно зависимости, которые нам нужно добавить в наш проект. В моем случае, поскольку я использую Maven, зависимости, которые мне нужно добавить в файл pom.xml, будут выглядеть следующим образом:

<dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>        <version>8.0.23</version></dependency>

Первая зависимость, которую мы здесь видим, это зависимость из пакета Spring. Здесь мы можем найти класс JdbcTemplate, который мы будем использовать для коммуникации с базой данных. Этот класс содержит полезные методы для обновления или получения данных из базы данных. Вторая зависимость требуется для связи с инстансом MySQL.

Примечание: эти зависимости имеют последнюю доступную на момент написания статьи версию (какую вы можете увидеть в репозитории Maven). Версия mysql-connector-java должна быть синхронизирована с версией инстанса MySQL, на котором работает ваша база данных. В моем случае, мой сервер MySQL имеет версию > 8, поэтому версия моего mysql-connector-java также выше чем 8.

Подключение к базе данных

После того как мы разобрались с зависимостями, мы можем установить связь с нашей базой данных. Мы могли бы написать код, необходимый для этой операции, в нашем тесте. Однако нам обязательно понадобится этот код и в других тестовых классах. Следовательно, этот код может быть написан либо в специальном классе для работы с базой данных, либо в базовом классе, расширяемом вашими тестами. Независимо от того, какой вариант вы выберете, соединение может быть установлено с помощью подобного метода:

public DataSource mysqlDataSource() {    DriverManagerDataSource dataSource = new DriverManagerDataSource();    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");    dataSource.setUrl("jdbc:mysql://dbURL:portNumber/nameOfDB?useSSL=false");    dataSource.setUsername("username");    dataSource.setPassword("password");    return dataSource;}

Первое, на что нам нужно обратить внимание, это то, что этот метод возвращает DataSource. Это необходимо для инициализации класса JdbcTemplate, который мы будем использовать в наших тестах, поскольку он хранит соединение с базой данных.

Затем в качестве имени класса драйвера в этом примере я использовал значение com.mysql.cj.jdbc.Driver. Опять же это требуется для установки соединения, и в некоторых случаях в более старых версиях зависимостей коннектора MySQL вместо него следует использовать com.mysql.jdbc.Driver. Если вы используете неправильное имя, вы получите соответствующее предупреждение при попытке подключения к базе данных.

Вам нужно будет указать расположение базы данных в методе setUrl. Он состоит из URL-адреса, порта и имени базы данных. И, конечно же, вам необходимо указать имя пользователя и пароль для подключения к базе данных, с помощью методов setUsername и setPassword.

Теперь, когда соединение установлено, нам нужно инициализировать класс JdbcTemplate. Мы можем объявить переменную этого типа в нашем тестовом классе:

private JdbcTemplate jdbcTemplate;

Затем в методе @BeforeAll мы можем инициализировать эту переменную, предоставив соединение, которое мы установили с базой данных:

jdbcTemplate = new JdbcTemplate(nameOfClass.mysqlDataSource());

На этом настройка завершена, соединение установлено, и мы можем начать обновление (updating) или запрашивание (querying) базы данных.

Update

В классе JdbcTemplate мы можем найти много полезных методов. Один из них, update, может быть использован для создания и обновления таблиц, добавления в них данных или даже удаления данных. Существует несколько вариантов этого метода (с разными сигнатурами), но тот, который я приведу здесь в качестве примера, принимает один параметр: SQL-запрос в виде String.

Пример

Создадим две новые таблицы: одну с именем meal (блюдо) и ingredient (ингредиент). В таблице meal мы хотим хранить название блюда, присвоенную ему категорию (представляющую, будь то завтрак, обед или ужин) и автоматически сгенерированный id в качестве первичного ключа (primary key). Для создания таблицы напишем в тестовом методе следующий код:

jdbcTemplate.update("create table meal(\n" +                 " meal_id bigint auto_increment primary key,\n" +                 " name varchar(50) not null unique,\n" +                 " category varchar(50) not null\n" + ");");

Когда мы запустим тест, таблица будет создана. Для создания таблицы больше ничего не требуется. Допустим, мы также хотим добавить к этому столу два блюда: фахитас из курицы и энчилада. Сделать это легко просто передадим требуемый SQL-запрос в метод update следующим образом:

jdbcTemplate.update("insert into meal (name, category) values ('Chicken Fajita', 'lunch');");jdbcTemplate.update("insert into meal (name, category) values ('Enchilada', 'lunch');");

Как видите, у нас по одному вызову метода update на одну SQL-операцию.

Теперь давайте создадим таблицу под названием ingredient. У нее не будет автоматически сгенерированного первичного ключа. Однако у нее будет внешний ключ (foreign key), соответствующий значению meal_id из таблицы meal. Каждая запись в этой таблице представляет собой ингредиент, соответствующий блюду из таблицы meal. Этот внешний ключ свяжет ингредиент с блюдом. Кроме того, в таблице ingredient есть столбцы для хранения названия ингредиента (name), количества (quantity) и единицы измерения (uom - unit of measure) для количества ингредиента.

Для того чтобы создать эту таблицу, а затем добавить к ней внешний ключ, мы снова будем использовать метод update, которому мы передадим соответствующий SQL-запрос:

jdbcTemplate.update("create table ingredient(\n" +         " meal_id bigint not null,\n" +         " name varchar(50) not null,\n" +         " quantity bigint not null,\n" +         " uom varchar(50) not null\n" + ");");jdbcTemplate.update("alter table ingredient add foreign key (meal_id)" +         " references meal(meal_id);\n");

Чтобы иметь больше данных для наших следующих примеров, я также добавлю некоторые данные в таблицу ingredient:

jdbcTemplate.update("insert into ingredient (meal_id, name, quantity,"                + " uom) values ((select meal_id from meal where name = 'Chicken Fajita'), 'chicken', 1, 'kg');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity, uom) " +                "values ((select meal_id from meal where name = 'Chicken Fajita'), 'red pepper', 1, 'piece');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity, uom) " +                "values ((select meal_id from meal where name = 'Chicken Fajita'), 'green pepper', 1, 'piece');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity, uom) " +                "values ((select meal_id from meal where name = 'Chicken Fajita'), 'yellow pepper', 1, 'piece');");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity," + " uom) " +                "values ((select meal_id from meal where name = " + "'Enchilada'), 'chicken', 1, 'kg');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity," + " uom) " +                "values ((select meal_id from meal where name = " + "'Enchilada'), 'cheese', 100, 'grams');\n");        jdbcTemplate.update("insert into ingredient (meal_id, name, quantity," + " uom) " +                "values ((select meal_id from meal where name = " + "'Enchilada'), 'tomato', 1, 'piece');\n");

Отлично, у нас есть 2 таблицы с данными, которые мы можем запрашивать. Теперь же мы будем использовать разные методы из класса JdbcTemplate для получения результатов разных типов.

queryForObject получить одно значение

Если нам нужно запросить из базы данных одно значение, мы можем использовать метод queryForObject. У этого метода также есть несколько вариантов использования, но здесь мы рассмотрим наиболее простой:

jdbcTemplate.queryForObject(String sqlStatement, Class returnType);

При вызове этого метода нам нужно указать, какой тип возвращаемого значения должен иметь запрос (Class). Мы могли бы, например, получить значение String (указав String.class) или целое число (указав Integer.class).

Пример

Нам нужно запросить базу данных, чтобы получить значение meal_id из таблицы meal для блюда Chicken Fajita. Нам нужно сохранить этот результат в переменной с типом int:

int id = jdbcTemplate.queryForObject("select meal_id from meal where name='Chicken Fajita';", Integer.class);

Здесь вы можете видеть, что тип возвращаемого значения запроса указан как Integer.class, поэтому результат сохраняется в переменной с типом int. Допустим, в тесте мы также хотим вывести в консоль результат этого запроса:

System.out.println("Meal id for Chicken Fajita = " + id);

Результатом этого вывода будет:

Meal id for Chicken Fajita = 1

queryForMap получить строку

Теперь предположим, что вы хотите получить целую строку из таблицы. Или части строки. Вы можете сделать это с помощью метода queryForMap, которому вы передаете необходимый SQL-запрос:

jdbcTemplate.queryForMap(String sqlStatement);

Результат этого запроса можно сохранить в переменной типа Map. Ключи map будут соответствовать имени каждого столбца, которому принадлежит элемент строки. Значение будет соответствовать фактическому значению из строки, соответствующей этому столбцу.

Пример

Мы хотим извлечь все данные о блюде с id 1 из таблицы meal, сохранить их в переменной и вывести результат в консоль. Это легко можно сделать следующим образом:

Map<String, Object> entireRowAsMap = jdbcTemplate.queryForMap("select * from meal where meal_id = 1");System.out.println("All details of meal with id 1 = " + entireRowAsMap);

Как видите, переменная entireRowAsMap представляет Map, ключи которой String, а значения Object. Это происходит потому, что некоторые значения являются целыми числами, некоторые строками и, конечно же, все эти типы являются объектами в Java. Вывод в консоль для приведенного выше кода:

All details of meal with id 1 = {meal_id=1, name=Chicken Fajita, category=lunch}

queryForList получить столбец

Когда вам нужно получить либо все значения, либо часть значений из конкретного столбца, вы можете использовать метод queryForList. В этом варианте использования я покажу на примере, что для результирующих элементов требуется SQL-запрос и тип возвращаемого значения. Речь идет о типе элементов, которые вы будете сохранять в список (List) Java. Например, если все элементы, которые вы извлекаете с помощью этого запроса, являются целыми числами, типом возврата будет Integer.class. Основной пример использования метода выглядит так:

jdbcTemplate.queryForList(String sqlStatement, Class returnType);

Пример

Мы хотим сохранить в список Java все названия ингредиентов, которые есть в таблице ingredient. Мы также хотим вывести эти значения в консоль. Этого можно добиться следующим образом:

List<String> queryForColumn = jdbcTemplate.queryForList("select " +         "distinct name from ingredient", String.class);System.out.println("All available ingredients = " + queryForColumn);

Поскольку все названия ингредиентов имеют тип String, тип возвращаемого значения для метода queryForList String.class. Вот что будет выведено на консоль:

All available ingredients = [chicken, red pepper, green pepper, yellow pepper, cheese, tomato]

queryForList получение списка строк

Другой вариант использования метода queryForList получение сразу нескольких строк. В этом случае единственный параметр, требуемый при вызове этого метода, это SQL-запрос, который собирает данные. Типом возврата будет список элементов типа map, где каждая map будет иметь ключ с типом String и соответствующее значение с типом Object. Этот метод выглядит так:

jdbcTemplate.queryForList(String sqlStatement);

Пример

Выберите все значения из таблицы meal, сохраните и выведите их в консоль.

List<Map<String, Object>> severalRowsAsListOfMaps = jdbcTemplate.queryForList("select * from meal;"); System.out.println("All available meals = " + severalRowsAsListOfMaps);

Вывод здесь представляет собой список элементов типа map:

All available meals = [{meal_id=1, name=Chicken Fajita, category=lunch}, {meal_id=2, name=Enchilada, category=lunch}]

Передача параметров запросам

В некоторых случаях SQL-запросы нуждаются в передаче параметра для замены захардкоженного значения из запроса. Например, вы можете захотеть выполнить тот же запрос для поиска строки в базе данных на основе ее id. Но вам может потребоваться передать id в тест через DataProvider. Следовательно, при каждом запуске метода для выполнения запроса у вас будет другое значение id.

В этом случае для любого запроса, который вы хотите выполнить, вместо указания явного значения вы проставляете символ ?. Это нужно сделать внутри SQL-запроса.

Пример

В тестовом методе нам нужно выяснить, сколько строк существует в таблице ingredient с именем, которое предоставляется в виде параметра. Результат этого запроса будет сохранен в переменной типа int и будет выведен в консоль. Этого можно добиться следующим образом:

Integer howManyUsages = jdbcTemplate.queryForObject("select count(*) "                 + "from ingredient where name=?", Integer.class, ingredientToLookFor);        System.out.println("How many time does the ingredient passed as "                 + "parameter appear in the DB " + " = " + howManyUsages);

Второй параметр, переданный методу queryForObject, это тип возвращаемого значения для запроса, а третий параметр это имя параметра, который будет отправлен в запрос из DataProvider. Например, если значение параметра ingredientToLookFor будет chicken, вывод в консоль будет следующим:

How many time does the ingredient passed as parameter appear in the DB = 2

Извлечение данных в объект Java

Помните мою статью об использовании объектов Java для моделирования данных, извлеченных из БД? Вы можете легко использовать JdbcTemplate для запроса базы данных и извлечения результата непосредственно в объект (Object). Все, что вам нужно для выполнения этой задачи, это объект Java для моделирования данных; класс преобразователя строк (row mapper), который сопоставляет столбец из базы данных со свойствами объекта; запрос, который извлекает данные в объект с помощью преобразователя строк.

Пример

Допустим, нам нужно смоделировать данные, соответствующие ингредиенту, название которого содержит текст yellow, в объект ингредиента (Ingredient Object). Это означает, что мы хотим, чтобы объект имел те же свойства, что и ингредиент в таблице. Мы хотим сопоставить каждый столбец со свойством. Поэтому мы создадим объект Java под названием Ingredient. Его свойства будут следующими:

public int meal_id; public String name; public int quantity; public String uom;

Рекомендуется синхронизировать имена свойств с именами столбцов базы данных. Таким образом, вы можете легко идентифицировать каждое свойство. Поскольку это объект, нам потребуется создать методы equals, hashCode и toString. Пока я пропущу эту часть.

Вместо этого я покажу кое-что еще, что вам понадобиться, а именно сеттеры для каждого свойства. Вы можете легко автоматически сгенерировать их в IntelliJ, используя в редакторе сочетание клавиш Alt+Insert. Они будут выглядеть следующим образом:

public void setMeal_id ( int meal_id){        this.meal_id = meal_id;}        public void setName (String name){          this.name = name;}        public void setQuantity ( int quantity){            this.quantity = quantity;        }        public void setUom (String uom){            this.uom = uom;        }

Вы будете использовать их для отображения данных БД в свойства объекта. И это произойдет внутри класса преобразователя строк, который мы создадим следующим. Тело этого класса выглядит следующим образом:

public class IngredientRowMapper implements RowMapper<Ingredient> {@Override        public Ingredient mapRow(ResultSet rs, int rowNum) throws SQLException {          Ingredient ingredient = new Ingredient();          ingredient.setMeal_id(rs.getInt("meal_id"));          ingredient.setName(rs.getString("name"));          ingredient.setQuantity(rs.getInt("quantity"));          ingredient.setUom(rs.getString("uom"));          return ingredient;        }}

Как видите, этот класс должен реализовать интерфейс под названием RowMapper. Из-за этого нам потребуется реализовать метод mapRow. И внутри этого метода вы будете сопоставлять каждое свойство объекта со столбцом базы данных, используя сеттеры. Так, например, для свойства quantity метод setQuantity установит значение, извлеченное из строки, имя соответствующего столбца которой тоже quantity.

Создав класс IntegerRowMapper, мы можем выполнить поставленную задачу, используя queryForObject для извлечения данных, соответствующих желтому (yellow) ингредиенту:

Ingredient ingredient = jdbcTemplate.queryForObject("select * from "         + "ingredient where name like '%yellow%'", new IngredientRowMapper());System.out.println("The ingredient object = " + ingredient);

Результат этого запроса будет отображаться как объект с соответствующими свойствами:

The ingredient object = Ingredient{meal_id=1, name='yellow pepper', quantity=1, uom='piece'}

Заключение

Мы рассмотрели несколько способов работы с базами данных из наших автоматизированных тестов: нужно ли нам обновлять их или просто извлекать данные в классе JdbcTemplate есть множество методов, которые могут нам помочь с этим. Вы можете выбрать, какой из них использовать, в зависимости от того, что должен возвращать ваш SQL-запрос.


Узнать подробнее о курсе "Java QA Automation Engineer".

Смотреть открытый вебинар на тему HTTP. Postman, Newman, Fiddler (Charles), curl, SOAP. SoapUI.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru