Author Archive

Пишем на C++ свою агрегационную функцию для HP Vertica

Предыстория

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

У каждого поставщика есть перечень товаров с указанием регионов поставки.
Есть таблица товаров, назовем ее product.
Регионов в Казахстане всего 17, поэтому я решил хранить данные о регионах поставки в одном числовом поле c помощью битов.

Таблица product выглядит следующим образом:

CREATE TABLE product (
	code VARCHAR(20),
	name VARCHAR(50),
	regions BIGINT,
	company_code VARCHAR(20),
	company_name VARCHAR(50)
);

code — код продукта;
name — наименование продукта;
regions — регионы поставки;
company_code — код компании;
company_name — наименование компании;

Теперь о битовом хранение регионов. Каждому региону соответствует свой бит:

Число

Битовое представление числа

Наименование региона

1

000000000000000001

По всей территории РК

2

000000000000000010

г. Астана

4

000000000000000100

г. Алматы

8

000000000000001000

Акмолинская область

16

000000000000010000

Актюбинская область

32

000000000000100000

Алматинская область

64

000000000001000000

Атырауская область

128

000000000010000000

Западно-Казахстанская область

256

000000000100000000

Жамбылская область

512

000000001000000000

Карагандинская область

1024

000000010000000000

Костанайская область

2048

000000100000000000

Кызылординская область

4096

000001000000000000

Мангистауская область

8192

000010000000000000

Туркестанская область

16384

000100000000000000

Павлодарская область

32768

001000000000000000

Северо-Казахстанская область

65536

010000000000000000

Восточно-Казахстанская область

131072

100000000000000000

г. Шымкент

Например, если нужно указать, что товар поставляется в три региона: г. Астана, г. Алматы и Карагандинскую область, то нужно применить бинарную операцию побитового «ИЛИ» к битам регионов, в итоге регионы закодируются в число 518:

ИЛИ

000000000000000010

2

000000000000000100

4

000000001000000000

512

Результат

000000001000000110

518

Заполним таблицу тестовыми данными:

INSERT INTO product VALUES ('001', 'Трансформатор тока', 6, 'abc', 'Energy Corp.');
INSERT INTO product VALUES ('001', 'Трансформатор тока', 2, 'xyz', 'Power Inc.');
INSERT INTO product VALUES ('002', 'Усилитель низкой частоты', 1, 'abc', 'Energy Corp.');
INSERT INTO product VALUES ('003', 'Набор резисторов', 20, 'abc', 'Energy Corp.');
INSERT INTO product VALUES ('003', 'Набор резисторов', 160, 'volt', 'Volt Group');
INSERT INTO product VALUES ('003', 'Набор резисторов', 36, 'xyz', 'Power Inc.');

Данные готовы, но проблема в том, что в Vertica нет подходящей агрегационной функции, позволяющей делать побитовое «ИЛИ» чисел. В Vertica есть функция bit_or, но по ряду причин она не подходит:

  1. Функция bit_or работает с бинарными данными
  2. Напрямую преобразовать число в бинарный вид нет возможности, только через hex
  3. Функция bit_or производит побитовые операции слева направо, поэтому если битов не хватает правая часть дополняется нулями, что дает не тот результат, который мы ожидаем.

Таким образом, мы пришли к необходимости создания своей функции.
Vertica позволяется писать свои функции на разных языках: C++, Java и Python.
Но именного агрегационные функции User Defined Aggregate Function (UDAF) можно почему-то писать только на С++.

В каталоге, где вы установили Vertica, есть SDK с примерами.
Например, в CentOS 7 это директория: /opt/vertica/sdk/examples

У вас должна быть установлена среда разработки g++.
Чтобы установить среду разработки g++, запустите:

yum install gcc gcc-c++ make

Если у вас Ubuntu, библиотеки могу собираться, но не работать выдавая ошибки вида:

Failure in UDx RPC call InvokeGetLibraryManifest()

Насколько я понял, это из-за версий gcc и g++, которые в Ubuntu по умолчанию 5.4.0, а в Centos 7 они по умолчанию 4.8.5.
Поэтому, если у вас возникли подобные проблемы, можно доставить нужные версии gcc и g++:

sudo apt-get install gcc-4.8
sudo apt-get install g++-4.8

Создайте файл OrSum.cpp со следующим содержимым:

 
#include "Vertica.h"
#include <time.h>
#include <sstream>
#include <iostream>
 
using namespace Vertica;
 
class OrSum : public AggregateFunction
{
 
public:
 
    virtual void initAggregate(ServerInterface &srvInterface,
                               IntermediateAggs &aggs)
    {
        vint &sum = aggs.getIntRef(0);
        sum = 0;
    }
 
    void aggregate(ServerInterface &srvInterface,
                   BlockReader &arg_reader,
                   IntermediateAggs &aggs)
    {
        vint &sum = aggs.getIntRef(0);
        do {
            const vint &val = arg_reader.getIntRef(0);
            if (val != Vertica::vint_null) {
                sum |= val;
            }
        } while (arg_reader.next());
    }
 
    virtual void combine(ServerInterface &srvInterface,
                         IntermediateAggs &aggs,
                         MultipleIntermediateAggs &aggs_other)
    {
        vint &sum = aggs.getIntRef(0);
 
        do {
            const vint &otherSum = aggs_other.getIntRef(0);
            if (otherSum != Vertica::vint_null) {
                sum |= otherSum;
            }
        } while (aggs_other.next());
    }
 
    virtual void terminate(ServerInterface &srvInterface,
                           BlockWriter &res_writer,
                           IntermediateAggs &aggs)
    {
        res_writer.setInt(aggs.getIntRef(0));
    }
 
    InlineAggregate()
};
 
class OrSumFactory : public AggregateFunctionFactory
{
    virtual void getPrototype(ServerInterface &srvInterface,
                              ColumnTypes &argTypes,
                              ColumnTypes &returnType)
    {
        argTypes.addInt();
        returnType.addInt();
    }
 
    virtual void getReturnType(ServerInterface &srvInterface,
                               const SizedColumnTypes &input_types,
                               SizedColumnTypes &output_types)
    {
        output_types.addInt();
    }
 
    virtual void getIntermediateTypes(ServerInterface &srvInterface,
                                      const SizedColumnTypes &input_types,
                                      SizedColumnTypes &intermediateTypeMetaData)
    {
        intermediateTypeMetaData.addInt();
    }
 
    virtual AggregateFunction *createAggregateFunction(ServerInterface &srvInterface)
    { return vt_createFuncObject<OrSum>(srvInterface.allocator); }
};
 
RegisterFactory(OrSumFactory);

Сборка библиотеки
В разработке…

Создание функции

CREATE OR REPLACE LIBRARY KESH_LIB AS '/opt/vertica/sdk/examples/build/KeshFunctions.so' LANGUAGE 'C++';
CREATE OR REPLACE AGGREGATE FUNCTION KESH_OR_SUM AS LANGUAGE 'C++' NAME 'KeshOrSumFactory' LIBRARY EPROC_LIB;

Использование функции

SELECT code, kesh_or_sum(regions) FROM product GROUP BY code

Удаление функции

DROP AGGREGATE FUNCTION KESH_OR_SUM(BIGINT);
DROP LIBRARY KESH_LIB;

Оптимизация Grok (Logstash)

Повествование о том, как неэффективный разбор grok забил процессоры на 100%.

Немного предыстории. С начала года у нас было настроено расширение к PostgreSQL под названием pg_audit (см. https://github.com/pgaudit/pgaudit) для аудита доступа к таблицам и колонкам таблиц. Логи pg_audit отправляются filebeat’ом в logstash, где разбираются через grok.
На тот момент grok-выражение было следующим:
%{TIMESTAMP_ISO8601:pg_aud_log_date} %{DATA:pg_aud_time_zone} %{DATA:pg_aud_db_name} %{DATA:pg_aud_db_user} %{DATA:pg_aud_db_host}: LOG:%{SPACE}AUDIT: %{DATA:pg_aud_audit_type},%{INT:pg_aud_statement_id},%{INT:pg_aud_sub_statement_id},%{DATA:pg_aud_class},%{DATA:pg_aud_command},%{DATA:pg_aud_object_type},%{DATA:pg_aud_object_name},%{GREEDYDATA:pg_aud_statement}>

В течении 5 месяцев все было нормально, пока в последние несколько дней CPU не достиг 100% и не спадал.

В логах logstash были следующие ошибки:

Timeout executing grok

Остро встала задача оптимизиции. Почему-то в интернете тяжело найти grok-паттерны.
Чтобы это исправить, приложу две ссылки и сами grok-паттерны внизу данной статьи.

Финальное оттюненное выражение имеет вид:
%{TIMESTAMP_ISO8601:pg_aud_log_date} %{NOTSPACE:pg_aud_time_zone} %{NOTSPACE:pg_aud_db_name} %{NOTSPACE:pg_aud_db_user} %{IPORHOST:pg_aud_db_host}: LOG:%{SPACE}AUDIT: %{WORD:pg_aud_audit_type},%{INT:pg_aud_statement_id},%{INT:pg_aud_sub_statement_id},%{WORD:pg_aud_class},%{WORD:pg_aud_command},%{WORD:pg_aud_object_type},%{USERNAME:pg_aud_object_name},%{GREEDYDATA:pg_aud_statement}

Видно, что заменены все DATA на более жесткие паттерны и оставлен только один GREEDYDATA.
Если у вас тоже проблемы с производительностью разбора grok, постарайтесь свести к минимуму использование паттернов DATA и GREEDYDATA.

Описанные мероприятия нормализовали работу logstash:

При создании grok-выражений очень помогает онлайн grokdebug:
http://grokdebug.herokuapp.com/

Совет
Начинайте писать выражение с

%{GREEDYDATA:pg_aud_statement}

постепенно выделяя нужные фрагменты записи лога.

Grok-паттерны

https://github.com/hpcugent/logstash-patterns/blob/master/files/grok-patterns
https://github.com/elastic/logstash/blob/v1.4.2/patterns/grok-patterns

USERNAME [a-zA-Z0-9._-]+
USER %{USERNAME}
INT (?:[+-]?(?:[0-9]+))
BASE10NUM (?[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
NUMBER (?:%{BASE10NUM})
BASE16NUM (?(?"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))
UUID [A-Fa-f0-9]{8}-(?:[A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}

# Networking
MAC (?:%{CISCOMAC}|%{WINDOWSMAC}|%{COMMONMAC})
CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4})
WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})
COMMONMAC (?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})
IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?
IPV4 (?/(?>[\w_%!$@:.,-]+|\\.)*)+
TTY (?:/dev/(pts|tty([pq])?)(\w+)?/?(?:[0-9]+))
WINPATH (?>[A-Za-z]+:|\\)(?:\\[^\\?*]*)+
URIPROTO [A-Za-z]+(\+[A-Za-z+]+)?
URIHOST %{IPORHOST}(?::%{POSINT:port})?
# uripath comes loosely from RFC1738, but mostly from what Firefox
# doesn't turn into %XX
URIPATH (?:/[A-Za-z0-9$.+!*'(){},~:;=@#%_\-]*)+
#URIPARAM \?(?:[A-Za-z0-9]+(?:=(?:[^&]*))?(?:&(?:[A-Za-z0-9]+(?:=(?:[^&]*))?)?)*)?
URIPARAM \?[A-Za-z0-9$.+!*'|(){},~@#%&/=:;_?\-\[\]]*
URIPATHPARAM %{URIPATH}(?:%{URIPARAM})?
URI %{URIPROTO}://(?:%{USER}(?::[^@]*)?@)?(?:%{URIHOST})?(?:%{URIPATHPARAM})?

# Months: January, Feb, 3, 03, 12, December
MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b
MONTHNUM (?:0?[1-9]|1[0-2])
MONTHNUM2 (?:0[1-9]|1[0-2])
MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])

# Days: Monday, Tue, Thu, etc...
DAY (?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)

# Years?
YEAR (?>\d\d){1,2}
HOUR (?:2[0123]|[01]?[0-9])
MINUTE (?:[0-5][0-9])
# '60' is a leap second in most time standards and thus is valid.
SECOND (?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?)
TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])
# datestamp is YYYY/MM/DD-HH:MM:SS.UUUU (or something like it)
DATE_US %{MONTHNUM}[/-]%{MONTHDAY}[/-]%{YEAR}
DATE_EU %{MONTHDAY}[./-]%{MONTHNUM}[./-]%{YEAR}
ISO8601_TIMEZONE (?:Z|[+-]%{HOUR}(?::?%{MINUTE}))
ISO8601_SECOND (?:%{SECOND}|60)
TIMESTAMP_ISO8601 %{YEAR}-%{MONTHNUM}-%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}(?::?%{SECOND})?%{ISO8601_TIMEZONE}?
DATE %{DATE_US}|%{DATE_EU}
DATESTAMP %{DATE}[- ]%{TIME}
TZ (?:[PMCE][SD]T|UTC)
DATESTAMP_RFC822 %{DAY} %{MONTH} %{MONTHDAY} %{YEAR} %{TIME} %{TZ}
DATESTAMP_RFC2822 %{DAY}, %{MONTHDAY} %{MONTH} %{YEAR} %{TIME} %{ISO8601_TIMEZONE}
DATESTAMP_OTHER %{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{TZ} %{YEAR}
DATESTAMP_EVENTLOG %{YEAR}%{MONTHNUM2}%{MONTHDAY}%{HOUR}%{MINUTE}%{SECOND}

# Syslog Dates: Month Day HH:MM:SS
SYSLOGTIMESTAMP %{MONTH} +%{MONTHDAY} %{TIME}
PROG (?:[\w._/%-]+)
SYSLOGPROG %{PROG:program}(?:\[%{POSINT:pid}\])?
SYSLOGHOST %{IPORHOST}
SYSLOGFACILITY <%{NONNEGINT:facility}.%{NONNEGINT:priority}>
HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}

# Shortcuts
QS %{QUOTEDSTRING}

# Log formats
SYSLOGBASE %{SYSLOGTIMESTAMP:timestamp} (?:%{SYSLOGFACILITY} )?%{SYSLOGHOST:logsource} %{SYSLOGPROG}:
COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)
COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}

# Log Levels
LOGLEVEL ([Aa]lert|ALERT|[Tt]race|TRACE|[Dd]ebug|DEBUG|[Nn]otice|NOTICE|[Ii]nfo|INFO|[Ww]arn?(?:ing)?|WARN?(?:ING)?|[Ee]rr?(?:or)?|ERR?(?:OR)?|[Cc]rit?(?:ical)?|CRIT?(?:ICAL)?|[Ff]atal|FATAL|[Ss]evere|SEVERE|EMERG(?:ENCY)?|[Ee]merg(?:ency)?)

Просмотр привилегий пользователей к таблицам

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

SELECT rtg.grantee, rtg.privilege_type, t.table_name 
FROM information_schema.tables t
JOIN information_schema.role_table_grants rtg ON t.table_name = rtg.table_name
ORDER BY rtg.grantee

Результат запроса содержит логин пользователя, тип привилегии и название таблицы:

Установка PostgreSQL из исходников на Ubuntu 16.04

Склонируйте репозиторий PostgreSQL

git clone https://github.com/postgres/postgres.git

После окончания загрузки файлов перейдите в созданную директорию

cd postgres

Переключитесь на ветку REL_10_STABLE

git checkout REL_10_STABLE

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

sudo apt-get install libreadline-dev
sudo apt-get install bison
sudo apt-get install flex

Сконфигурируйте проект

./configure

Соберите проект

make

Установить postgres

sudo make install

Переключитесь в режим супер-пользователя:

sudo su

Добавьте нового пользователя postgres:

adduser postgres

Задайте пароль для пользователя postgres:

passwd postgres

Создайте директорию для данных postgres:

mkdir /usr/local/pgsql/data

Поменяйте владельца и группу для директории:

chown postgres:postgres /usr/local/pgsql/data

Далее поменяйте пользователя на postgres:

su - postgres

Проинициализируйте новый кластер баз данных:

/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data/

Установка Kubuntu c установочной флешки

Делаем на Kubuntu установочную флешку для установки Kubuntu (простите за тавтологию).
Для начала устанавливаем UNetbootin на странице https://unetbootin.github.io/linux_download.html можно его скачать, там же есть инструкция по установке.
Вот так выглядит главное окно программы:

Записываемый на флешку образ можно скачать либо по сети, либо выбрать iso на вашем компьютере.

Первая проблема, которую можно поймать:
/dev/sdb1 не смонтирован

Необходимо сначала примонтировать USB-накопитель /dev/sdb1 к точке монтирования. 
Большинство дистрибутивов делают это автоматически после переподключения USB-накопителя.

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

Также, желательно, чтобы в названии флешки были только латинские буквы.

После того, как вы успешно записали флешку, вставили ее компьютер, отключили в BIOS режим UEFI, можете поймать ошибку

(initramfs) Unable to find a medium containing a live file system

Проблема заключается в использовании USB 3.0.
У меня на ноутбуке HP все три USB-порта версии 3.0.
Советуют в BIOS отключить поддержку 3.0 в пользу 2.0.
Но на этом ноуте нет такой опции в BIOS.
А флешка у меня 16 GB USB 3.0.

С флеш-картой USB 2.0 загружался установщик, но где-то посередите он падал с ошибкой.

В конце концов записал ISO на компакт-диск и установил с переносного DVD-ROM.

Target lists can have at most 1664 entries в PostgreSQL

Давно не писал… На прошлой неделе поймал такую ошибку:

ERROR: target lists can have at most 1664 entries

Данная ошибка возникла из-за того, что появилось много зависимостей между сущностями(Entity).
Когда идет запрос данных через Hibernate, то JOIN’ится много таблиц, и общее количество получаемых полей начинает превышать 1664. В нашем случае это было около 1700 полей. Такое разумное ограничение есть у PostgreSQL.
Решить можно такими путями:
1) Переписать запросы, взяв только используемые поля;
2) Сделать некоторые поля Lazy.
Скорее всего, такая проблема и у вас решится вторым путем.

Удаление числа из строки

Иногда мы пользуемся трюком, сохраняя числа (ID из базы данных) в строке через запятую.
Например: «1024,15,8,0,55».
И теперь, допустим, какое-то число из этого списка не нужно, и его необходимо убрать из исходной строки. Как это реализовать?

Первый вариант:
1) разбить строку по запятой
2) отфильтровать удаляемое число
3) собрать строку заново

Второй вариант (допустим, удаляем число 55):
1) вырезать из строки подстроки вида: ,55,
2) удаляем подстроки вида 55, и ,55

Реализация 1-го варианта (Java):

String val = "55";
String collect = Arrays.stream(
        "55,1024,55,15,8,0,55".split(","))
        .filter(v -> !v.equals(val))
        .collect(Collectors.joining(","));
System.out.println(collect);//1024,15,8,0

Реализация 2-го варианта (Java):

int del = 55;
String result = "55,1024,55,15,8,0,55"
        .replaceAll("," + del + ",", ",")
        .replaceAll(",?" + del + ",?", "");
System.out.println(result);//1024,15,8,0

Некоторые вещи про закачку файлов

В любом web-проекте в определенное время возникает вопрос как загружать/скачивать файлы. В этой статье рассмотрим важные аспекты этих процедур:

  • Можно загружать файлы не только в виде байт, но и формате base64.
  • Файл загружается на веб-сервер и сохраняется как временный файл
  • При передаче файла на сервер используется кодирование multipart/form-data
  • Из временного файла можно получить оригинальное название файла, байты файла, тип содержимого(ContentType), размер.
  • На основе байтов можно рассчитать хэш. Например, алгоритмом MD5, SHA-1 или SHA-256.
  • Хэш файла может пригодиться при отдаче файла, чтобы проверить целостность.
  • При отдаче файла следует указать HTTP-заголовок Content-Disposition. Параметр inline сообщает браузеру, что файл можно открыть непосредственно в браузере, если конечно позволяет тип файла. Параметр attachment сообщает, что файл должен скачиваться. Также в Content-Disposition передается наименование файла, которое лучше кодировать с помощью URL Encoding.
  • Если в браузере вы хотите отобрать процесс загрузки файла, например, в процентах, то проще всего это сделать через javascript, который может отслеживать кол-во отправленных байтов.

Бинарный алгоритм вычисления НОД

Наибольшим общим делителем (НОД) для двух целых чисел называется наибольший из их общих делителей.
Например: для чисел 8 и 12 наибольший общий делитель равен 4.

Ранее мы уже рассматривали нахождение НОД с помощью Алгоритма Евклида:

long gcd(long a,long b){
	return b == 0 ? a : gcd(b,a % b);		
}

Сейчас рассмотрим Бинарный алгоритм Евклида, который быстрее обычного алгоритма Евклида.
Бинарный алгоритм основан на следующих свойствах НОД:

НОД(2m, 2n) = 2 НОД(m, n),
НОД(2m, 2n+1) = НОД(m, 2n+1),
НОД(-m, n) = НОД(m, n)

Теперь реализуем этот алгоритм на Java:

static int gcd(int m, int n){
    if(m == 0) return n;
    if(n == 0) return m;
    if(n == m) return n;
    if(m == 1) return 1;
    if(n == 1) return 1;
    boolean em = (m & 1) == 0;
    boolean en = (n & 1) == 0;
    if(em && en) return gcd(m >> 1, n >> 1) << 1;
    if(em) return gcd(m >> 1, n);
    if(en) return gcd(m, n >> 1);
    if(n > m) return gcd((n - m) >> 1, m);
    return gcd((m - n) >> 1, n);
}

Для ускорения деления на два используется сдвиг битов на позицию вправо(>> 1), для умножения на два используется сдвиг на позицию влево (<< 1). Проверка четности числа осуществляется проверкой последнего бита числа (m & 1), если выражение равно 0, то число четно, иначе нечетно.

Как из Java обратиться к сервису по протоколу HTTPS

Это юбилейная 100-я статья!!!

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

Для начала открываем сайт в браузере:

В адресной строке браузера рядом с текстом https:// есть иконка, нажав на которую появится возможность просмотреть / скачать / экспортировать сертификат.
Нужно экспортировать сертификат в файл test.crt

Файл будет иметь примерно такое содержание:

-----BEGIN CERTIFICATE-----
MIIEdjCCA16gAwIBAgIUGyZeQnd4LMFso5FQwrzjHmrNWVswDQYJKoZIhvcNAQEF
utCwINC/0L7QtNC70LjQvdC90L7RgdGC0Lgg0YHQtdGA0LLQtdGA0LAwMgYDVR0f
KqOTBEhH50jwo6WaQIUrD54ElD5gVO3VIT+eAMZm0HzXF+NKpkNaiR1b
-----END CERTIFICATE-----

Далее необходимо выполнить следующую команду:

sudo keytool -import -alias yourname -file test.crt
 -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

-alias — произвольное уникальное название
-file — путь к файлу
-keystore — путь к хранилищу сертификатов Java
$JAVA_HOME — путь к домашней директории Java, которую вы используете для запуска вашего приложения
-storepass — пароль к хранилищу, по умолчанию changeit