Все записи автора Павел Грибов

WebSocket Ratchet сервер с одновременной прослушкой двух IP

Задача: поднять WebSocket сервер, одновременно слущающий и wss (для браузеров) и ws (для скриптов на PHP на том-же сервере).

Почему такой велосипед? Ну просто вот ну никак не удалось подобрать WebSocket клиент на PHP нормально работающий с WSS (SSL).

Решение:

$myClassMessage=new MessageService($UsersApi);

$loop = React\EventLoop\Factory::create();
$webSock = new React\Socket\Server('0.0.0.0:35500', $loop);
$webSock = new React\Socket\SecureServer($webSock, $loop, [
    'local_cert'        => 'епкуеп.crt', // path to your cert
    'local_pk'          => 'кепуке.key', // path to your server private key
    'allow_self_signed' => TRUE, // Allow self signed certs (should be false in production)
    'verify_peer' => FALSE
]);

$webServer = new Ratchet\Server\IoServer(
    new Ratchet\Http\HttpServer(
        new Ratchet\WebSocket\WsServer(
            $myClassMessage
        )
    ),
    $webSock,
    $loop        
);
$webSock2 = new React\Socket\Server('127.0.0.1:35501', $loop);

$webServer = new Ratchet\Server\IoServer(
    new Ratchet\Http\HttpServer(
        new Ratchet\WebSocket\WsServer(
            $myClassMessage
        )
    ),
    $webSock2,
    $loop        
);
$loop->run();

websocket сервер ratchet c SSL

Почемуто большинство примеров создания websocket сервера с использованием ratchet приведены без использования SSL.  И у всех как я почитаю пляски с буном потом с проксированием через apache или ngnix. Но ведь он умеет и без этого!

Вот примерно как это оформляется:

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React\EventLoop\Factory;
use React\Socket\SecureServer; 

$loop = React\EventLoop\Factory::create();
$webSock = new React\Socket\Server('0.0.0.0:33423', $loop);
$webSock = new React\Socket\SecureServer($webSock, $loop, [
    'local_cert'        => 'sssss.crt', 
    'local_pk'          => 'sssss.key', 
    'allow_self_signed' => TRUE, 
    'verify_peer' => FALSE
]);

$webServer = new Ratchet\Server\IoServer(
    new Ratchet\Http\HttpServer(
        new Ratchet\WebSocket\WsServer(
            new MessageService($UsersApi)
        )
    ),
    $webSock,
    $loop        
);

$webServer->run();

А вот пример класса MessageService:

class MessageService implements MessageComponentInterface {
    protected $clients;
    public $cnt=0; 
    public $uapi="";
    public function __construct($uu) {
        $this->uapi=$uu;
        $this->clients = array();        
        echo "- запустили, ждем соединения..\n";
    }

    public function onOpen(ConnectionInterface $conn) {                
            $user["user"]="";
            $user["conn"]=$conn;
            $this->clients[]=$user;
            $this->cnt++;            
            echo "--с нами ".count($this->clients)."\n";
            echo "---спрашиваю who_are_you?\n";
            $conn->send(json_encode(array("command"=>"who_are_you")));
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        echo "-- пришло сообщение: $msg \n";        
        $msg= json_decode($msg);
        //var_dump($msg);
        if (isset($msg->command)):
            switch ($msg->command) {
               case "who_am_i":
               // обхожу все соединения, и добавляю параметр - пришедший id пользователя
                    foreach ($this->clients as &$client) {
                        if ($from == $client["conn"]) {
                            $client["user"]=$msg->user;
                        };
                    };
               break;
               default:break;
            }
        endif;

    }

    public function onClose(ConnectionInterface $conn) { 
        $user="";
        foreach ($this->clients as $key=>$client) {          
          if ($client["conn"]==$conn):
              echo "-- ушел $key(".$client["user"].")\n";              
              $user=$client["user"];
              unset($this->clients[$key]);              
          endif;
        };
        echo "-- осталось соединений ".count($this->clients)."\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        $conn->close();
    }
}

Android WebView: не работает выбор файла на странице в теге input type=»file»

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

Решение:

public class MainActivity extends AppCompatActivity {
    private WebView mbrowser;
    private ValueCallback<Uri> mUploadMessage;
    public ValueCallback<Uri[]> uploadMessage;
    public static final int REQUEST_SELECT_FILE = 100;
    private final static int FILECHOOSER_RESULTCODE = 1;
...
...
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (requestCode == REQUEST_SELECT_FILE) {
                if (uploadMessage == null) return;
                uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));
                uploadMessage = null;
            }
        }
        else if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage) return;
            Uri result = intent == null || resultCode != MainActivity.RESULT_OK ? null : intent.getData();
            mUploadMessage.onReceiveValue(result);
            mUploadMessage = null;
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
...
...
        mbrowser=(WebView) findViewById(R.id.main_webview);
        mbrowser.getSettings().setJavaScriptEnabled(true); // разрешен javascript
        mbrowser.getSettings().setAppCacheEnabled(false); // разрешон кэш
        mbrowser.getSettings().setDatabaseEnabled(true); // хранение данных во встроенной БД в браузере
        mbrowser.getSettings().setDomStorageEnabled(true);
       // mbrowser.getSettings().setSupportZoom(true); // зум
        mbrowser.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); // разрешать открывать окна
        //mbrowser.getSettings().setBuiltInZoomControls(true); // приблизить/удалить
        mbrowser.getSettings().setGeolocationEnabled(true); // разрешаем геолокацию
        mbrowser.setWebChromeClient(new WebChromeClient() {
                                        @Override
                                        public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
                                            callback.invoke(origin, true, false);
                                        }
                                        // For 3.0+ Devices (Start)
                                        // onActivityResult attached before constructor
                                        protected void openFileChooser(ValueCallback uploadMsg, String acceptType) {
                                            mUploadMessage = uploadMsg;
                                            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                                            i.addCategory(Intent.CATEGORY_OPENABLE);
                                            i.setType("image/*");
                                            startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE);
                                        }
                                        // For Lollipop 5.0+ Devices
                                        public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
                                            if (uploadMessage != null) {
                                                uploadMessage.onReceiveValue(null);
                                                uploadMessage = null;
                                            }
                                            uploadMessage = filePathCallback;
                                            Intent intent = null;
                                            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                                                intent = fileChooserParams.createIntent();
                                            }
                                            try {
                                                startActivityForResult(intent, REQUEST_SELECT_FILE);
                                            } catch (ActivityNotFoundException e) {
                                                uploadMessage = null;
                                                return false;
                                            }
                                            return true;
                                        }
                                        //For Android 4.1 only
                                        protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
                                            mUploadMessage = uploadMsg;
                                            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
                                            intent.addCategory(Intent.CATEGORY_OPENABLE);
                                            intent.setType("image/*");
                                            startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE);
                                        }
                                        protected void openFileChooser(ValueCallback<Uri> uploadMsg) {
                                            mUploadMessage = uploadMsg;
                                            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                                            i.addCategory(Intent.CATEGORY_OPENABLE);
                                            i.setType("image/*");
                                            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
                                        }

        });

A PKCS #11 module returned CKR_DEVICE_ERROR, indicating that a problem has occurred with the token or slot.

Ходишь-ходишь в школу, а потом «бац — вторая смена»… Эмм я к тому что в 2020 поймать ошибку 2012 года, помеченную на CentOS как «закрытую».. Да еще и на PHP..

А дело вот в чем. На одном из проектов использую WebSocket сервер WorkMan, который имеет псевдомногопотоковость. Внутри потоков, активно использую вызовы curl_exec. Вот они то и вызывают ошибку «A PKCS #11 module returned CKR_DEVICE_ERROR, indicating that a problem has occurred with the token or slot.«, которая выводится при помощи curl_error. Описание ошибки было еще в далеком 2012 году:  https://bugzilla.redhat.com/show_bug.cgi?id=870856

Помечена как «закрытая». А вот и нет.. Хотя на Ubuntu не воспроизводится — только  «CentOS Linux release 7.8.2003»

Как лечим:

export NSS_STRICT_NOFORK=DISABLED

БСП 3.1.13 Добавление контактной информации в свой справочник

Задача: Внедрить в свой справочник контактную информацию (Например Телефон и Адрес)

Решение: используем стандартный функционал БСП

  1. В переопределяемые типы ВладелецКонтактнойИнформации добавляем справочник, куда нужно добавить контактную информацию. Если Не казан составной тип — указываем.
  2. В справочник  ВидыКонтактнойИнформации добавляем предопределенные значения: группа с указанием имени справочника и реквизиты. Например:

3. В справочник где хотим разместить контактную информацию, на форму добавляем группу «КонтактнаяИнформация»

4. В модуль формы справочника добавляем:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

//Стандартная подсистема Управление контактной информацией
 УправлениеКонтактнойИнформацией.ПриСозданииНаСервере(ЭтотОбъект, Объект, "ГруппаКонтактнаяИнформация", ПоложениеЗаголовкаЭлементаФормы.Лево);
//Конец Стандартная подсистема Управление контактной информацией

	// инициализация !! выполняем 1 раз, далее коментируем!
	
		//КМ_Организации = Справочники.ВидыКонтактнойИнформации.СправочникЭЗС.ПолучитьОбъект();
		//КМ_Организации.Используется = Истина;
		//ОбновлениеИнформационнойБазы.ЗаписатьДанные(КМ_Организации);

		//ПараметрыВида = УправлениеКонтактнойИнформацией.ПараметрыВидаКонтактнойИнформации(Перечисления.ТипыКонтактнойИнформации.Телефон);
		//ПараметрыВида.Вид = Справочники.ВидыКонтактнойИнформации.ТелефонЭЗС;
		//ПараметрыВида.МожноИзменятьСпособРедактирования = Истина;
		//ПараметрыВида.Порядок = 1;
		//УправлениеКонтактнойИнформацией.УстановитьСвойстваВидаКонтактнойИнформации(ПараметрыВида);
		//
		//ПараметрыВида = УправлениеКонтактнойИнформацией.ПараметрыВидаКонтактнойИнформации(Перечисления.ТипыКонтактнойИнформации.Адрес);
		//ПараметрыВида.Вид = Справочники.ВидыКонтактнойИнформации.АдресЭЗС;
		//ПараметрыВида.МожноИзменятьСпособРедактирования = Истина;
		//ПараметрыВида.Порядок = 2;
		//УправлениеКонтактнойИнформацией.УстановитьСвойстваВидаКонтактнойИнформации(ПараметрыВида);
	
	// конец инициализации!	
	
КонецПроцедуры

&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
	Если ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформацией = ОбщегоНазначения.ОбщийМодуль("УправлениеКонтактнойИнформацией");
		МодульУправлениеКонтактнойИнформацией.ПриЧтенииНаСервере(ЭтотОбъект, ТекущийОбъект);
	КонецЕсли;
	

КонецПроцедуры

&НаСервере
Процедура ОбработкаПроверкиЗаполненияНаСервере(Отказ, ПроверяемыеРеквизиты)
	Если ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформацией = ОбщегоНазначения.ОбщийМодуль("УправлениеКонтактнойИнформацией");
		МодульУправлениеКонтактнойИнформацией.ОбработкаПроверкиЗаполненияНаСервере(ЭтотОбъект, Объект, Отказ);
	КонецЕсли;

КонецПроцедуры

&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
	Если ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформацией = ОбщегоНазначения.ОбщийМодуль("УправлениеКонтактнойИнформацией");
		МодульУправлениеКонтактнойИнформацией.ПередЗаписьюНаСервере(ЭтотОбъект, ТекущийОбъект);
	КонецЕсли;

КонецПроцедуры


// СтандартныеПодсистемы.КонтактнаяИнформация

&НаКлиенте
Процедура Подключаемый_КонтактнаяИнформацияПриИзменении(Элемент)
	Если ОбщегоНазначенияКлиент.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформациейКлиент = ОбщегоНазначенияКлиент.ОбщийМодуль("УправлениеКонтактнойИнформациейКлиент");
		МодульУправлениеКонтактнойИнформациейКлиент.НачатьИзменение(ЭтотОбъект, Элемент);
	КонецЕсли;
КонецПроцедуры

// Параметры:
// 	Элемент - ПолеФормы
//


&НаКлиенте
Процедура Подключаемый_КонтактнаяИнформацияНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
	
	Если ОбщегоНазначенияКлиент.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		
		ПараметрыОткрытия = Новый Структура;
		
		Отбор = Новый Структура("ИмяРеквизита", Элемент.Имя);
		Строки = ЭтотОбъект.КонтактнаяИнформацияОписаниеДополнительныхРеквизитов.НайтиСтроки(Отбор);
		ДанныеСтроки = ?(Строки.Количество() = 0, Неопределено, Строки[0]);
		Если ДанныеСтроки <> Неопределено Тогда
			//ДобавитьСтрануВПараметрыОткрытия(ПараметрыОткрытия, ДанныеСтроки.Вид, Объект.СтранаРегистрации);
		КонецЕсли;
		
		МодульУправлениеКонтактнойИнформациейКлиент = ОбщегоНазначенияКлиент.ОбщийМодуль("УправлениеКонтактнойИнформациейКлиент");
		МодульУправлениеКонтактнойИнформациейКлиент.НачатьВыбор(ЭтотОбъект, Элемент,, СтандартнаяОбработка, ПараметрыОткрытия);
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура Подключаемый_КонтактнаяИнформацияПриНажатии(Элемент, СтандартнаяОбработка)
	Если ОбщегоНазначенияКлиент.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформациейКлиент = ОбщегоНазначенияКлиент.ОбщийМодуль("УправлениеКонтактнойИнформациейКлиент");
		МодульУправлениеКонтактнойИнформациейКлиент.НачатьВыбор(ЭтотОбъект, Элемент,, СтандартнаяОбработка);
	КонецЕсли;
КонецПроцедуры

// Параметры:
// 	Элемент - ПолеФормы
// 	СтандартнаяОбработка - Булево
//
&НаКлиенте
Процедура Подключаемый_КонтактнаяИнформацияОчистка(Элемент, СтандартнаяОбработка)
	Если ОбщегоНазначенияКлиент.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформациейКлиент = ОбщегоНазначенияКлиент.ОбщийМодуль("УправлениеКонтактнойИнформациейКлиент");
		МодульУправлениеКонтактнойИнформациейКлиент.НачатьОчистку(ЭтотОбъект, Элемент.Имя);
	КонецЕсли;
КонецПроцедуры


// Параметры:
// 	Команда - КомандаФормы
//
&НаКлиенте
Процедура Подключаемый_КонтактнаяИнформацияВыполнитьКоманду(Команда)
	Если ОбщегоНазначенияКлиент.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформациейКлиент = ОбщегоНазначенияКлиент.ОбщийМодуль("УправлениеКонтактнойИнформациейКлиент");
		МодульУправлениеКонтактнойИнформациейКлиент.НачатьВыполнениеКоманды(ЭтотОбъект, Команда.Имя);
	КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура Подключаемый_КонтактнаяИнформацияАвтоПодбор(Элемент, Текст, ДанныеВыбора, ПараметрыПолученияДанных, Ожидание, СтандартнаяОбработка)
	
	Если ОбщегоНазначенияКлиент.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформациейКлиент = ОбщегоНазначенияКлиент.ОбщийМодуль("УправлениеКонтактнойИнформациейКлиент");
		МодульУправлениеКонтактнойИнформациейКлиент.АвтоПодборАдреса(Элемент, Текст, ДанныеВыбора, ПараметрыПолученияДанных, Ожидание, СтандартнаяОбработка);
	КонецЕсли;
	
КонецПроцедуры

// Параметры:
// 	Элемент - ПолеФормы
// 	
&НаКлиенте
Процедура Подключаемый_КонтактнаяИнформацияОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
	
	Если ОбщегоНазначенияКлиент.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформациейКлиент = ОбщегоНазначенияКлиент.ОбщийМодуль("УправлениеКонтактнойИнформациейКлиент");
		МодульУправлениеКонтактнойИнформациейКлиент.ОбработкаВыбора(ЭтотОбъект, ВыбранноеЗначение, Элемент.Имя, СтандартнаяОбработка);
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура Подключаемый_КонтактнаяИнформацияОбработкаНавигационнойСсылки(Элемент, НавигационнаяСсылкаФорматированнойСтроки, СтандартнаяОбработка)
	
	Если ОбщегоНазначенияКлиент.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформациейКлиент = ОбщегоНазначенияКлиент.ОбщийМодуль("УправлениеКонтактнойИнформациейКлиент");
		МодульУправлениеКонтактнойИнформациейКлиент.НачатьОбработкуНавигационнойСсылки(ЭтотОбъект, Элемент, НавигационнаяСсылкаФорматированнойСтроки, СтандартнаяОбработка);
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура Подключаемый_ПродолжитьОбновлениеКонтактнойИнформации(Результат, ДополнительныеПараметры) Экспорт
	ОбновитьКонтактнуюИнформацию(Результат);
КонецПроцедуры

&НаСервере
Процедура ОбновитьКонтактнуюИнформацию(Результат)
	
	Если ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.КонтактнаяИнформация") Тогда
		МодульУправлениеКонтактнойИнформацией = ОбщегоНазначения.ОбщийМодуль("УправлениеКонтактнойИнформацией");
		МодульУправлениеКонтактнойИнформацией.ОбновитьКонтактнуюИнформацию(ЭтотОбъект, Объект, Результат);
	КонецЕсли;
КонецПроцедуры
// Конец СтандартныеПодсистемы

Внимание! Обращаю внимание что нужно на форме прописать событие «ПриСозданииНаСервере», а в самом событии закомментировать код, в котором написано, что выполнять 1 раз