понедельник, 30 декабря 2013 г.

Java EE: Делаем логи ещё удобнее

Где-то месяц назад я описывал простую систему логирования для web-приложений, которая позволяла, кроме всего прочего, группировать данные в логах в контексте одного запроса. Фича весьма удобная и я с удовольствием использую её в своих проектах. Но, как водится, в процессе эксплуатации вылезли и недоработки. Этот прост о том, как развивалась система, и о том, как она теперь работает.

Что было не так со старой системой?
Да, она группировала вывод в контексте одного запроса к серверу. Вернее в контексте одного потока, запущенного servlet-контейнером для обработки этого запроса. Если servlet не порождал новых потоков, то всё было нормально, но вот если новые потоки создавались, мы тут получали проблему. Логгер писал их отдельно, а значит, вперемешку с остальными потоками. Мелочь, а неприятно.

пятница, 15 ноября 2013 г.

Включаем multitouch и "natural scroll" для touchpad-а Dell Inspiron n5110

Сегодня переехал на "новый" Dell. Поставил свой любимый дистрибутив Elementary OS, с привычным удовольствием настроил софт, разложил всё по местам... И тут - разочарование: прокрутка двумя пальцами, к которой уже привык на предыдущем ноуте не работает. То, что система не предлагала эту опцию навело меня на мысль, что мой touchpad не определился как multitouch-устройство. А ведь это не так! Несколько минут поиска по форумам дали достаточно простое решение:
Скачиваем ALPS dlkm driver
Распаковываем его. Каталог psmouse-alps-1.3 с правами root копируем в /usr/src
Выполняем 
sudo dkms add /usr/src/psmouse-alps-1.3
sudo dkms autoinstall

sudo rmmod psmouse
sudo modprobe psmouse
После этого multitouch определяется корректно и мы можем включить нашу любимую опцию.
Если этого не случилось, попробуйте подменить 
/usr/src/psmouse-alps-1.3/src/alps.c исходниками взятыми отсюда и повторить процедуру. 
Собственно вся технология взята тут, судя по коментариям она отлично работает на большинстве linux-дистрибутивов.

Попутно нашёл ещё один полезный фокус для любителей этого няшного дистрибутива. Если вам нравится "natural scroll" в стиле Mac, в файле /usr/share/X11/xorg.conf.d/50-synaptics.conf добавляем строки:
Option "VertScrollDelta" "-111" 
Option "HorizScrollDelta" "-111"
так чтобы секция выглядела следующим образом:
Section “InputClass”
Identifier “touchpad catchall”
Driver “synaptics”
MatchIsTouchpad “on”
Option “VertScrollDelta” “-111”
Option “HorizScrollDelta” “-111”
MatchDevicePath “/dev/input/event*”
EndSection

среда, 13 ноября 2013 г.

"Вечный Service" в Android

Когда мы делаем приложения, мы руководствуемся самыми гумаными соображениями. Мы хотим облегчить жизнь нашим клиентам, помочь им, защитить, вооружить против любых проблем этого ужасного мира. Но клиенты почему-то не хотят покоряться нашему мудрому руководству. Закрывают наше приложения, останавливают его, находят в списке "запущенных процессов" через настройки и опять-таки останавливают. Ну, как дети малые, ей-богу ;) Если вы в своей всеобъемлющей мудрости хотите спасти своих клиентов от их самих, вам наверняка прийдётся защититься от их жалких попыток упавлять своим смартфоном. Как же это сделать? Перенести логику в Service, чтобы закрытие приложения не останавливало его работу? Недостаточно. Сделать в нём вечный цикл, вызвать startService() в onDestroy() - тоже. В конце концов эти неразумные существа могут перезагрузить смартфон и мы утратим над ними власть не сможем помогать им. Можно, конечно ловить событие загрузки (и ещё стопицот других системных событий) и поднимать наш сервис. Однако система на то и система, чтобы жить своими, непредсказуемыми событиями, а значит никакой гарантии, что эти события произойдут когда нам нужно, увы, нет. Как же нам быть? Ответ очевиден:

среда, 6 ноября 2013 г.

UDP в Android: как приложения ищут друг друга в сети?

В большинстве случаев говря о передаче данных по сети мы имеем в виду TCP. Для большинства сетевых задач этот протокол лучший, но он вообще-то совсем не единственный. И есть вещи, которые с его помощью делать не удобно. Например: мы знаем порт, но не знаем IP-адреса получателя и нам нужно его найти. Опрашивать все адреса локальной сети по очереди? Жуть... Тут надо бы послать широковещательное сообщение, а для этого мы уже используем UDP. Этот протокол достаточно интересен. Например, мы можем слать сообщения и слушать на одном и том же проту одновременно. UDP при отправке широковещательных сообщений не создаёт соединения в привычном нам смысле. Мы не знаем, доставлено ли получателю наше UDP сообщение. Впрочем нам это и не нужно. Передавать данные мы будем уже по TCP. Итак как же экземплярам нашего приложения искать друг друга?

среда, 30 октября 2013 г.

Java EE: Удобные логи - это просто

- Привет, тут у меня клиент, он не видит в архиве своих операций. Глянь плиз, что у него не так? Только быстрее, он VIP, он злой и висит на линии...

Если вы пишите серверные приложения, то такие просьбы девочек из саппорта вам знакомы. И знакомо то чувство отвращения, злости и отчаяния с которым вы бросаете свой незаконченный рефакторинг в самом неподходящем месте, чтобы залезть в консоль и смотреть логи.
А логи, как на зло пишутся по старинке, потому что разбираться с log4j некогда, да и неохота... Вот если бы сливать логи в базу, да запилить админку... Но когда этим заниматься? 100500 задач в разработке а тут ещё эти логи.
Как же быть? Выделить день-другой и переписать логирование на какой-нибудь фреймворк? Это можно. Но мы ведь программисты, давайте сначала оценим задачу. Итак чего бы нам хотелось?
  • Логи не должны писаться вперемешку. Все записи в контексте одного запроса должны в логах находиться в одном месте, "пакетом".
  • Все записи одного уровня должны маркироваться общим тегом
  • Вместе с пакетом записей должна быть возможность вывести в лог произвольные данные (например id клиента и т.п.), относящиеся к этому запросу. 
И всё? И для этих трёх простых вещей тащить в свой проект ещё один фреймврок? Нет уж, давайте напишим несколько строк кода и покончим с этим ;)

вторник, 29 октября 2013 г.

Настраиваем связку Tomcat+Nginx

Это сугубо практичный пост: просто набор команд для настройки "маленького сервера с блекджеком и танцовщицами" для хостинга java-приложений. Настраивал сегодня wds на Ubuntu 12.04 и, наверное, в десятый раз вспоминал всё это. Теперь всё будет в одном месте.

1. Java
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java6-installer


в конец ~/.bashrc добавляем 
export JAVA_HOME=/usr/lib/jvm/java-6-oracle 

2. Tomcat
sudo apt-get install tomcat7

Tomcat будет стартовать при старте системы, приложения будут разворачиваться на порту 8080 (каждое в своём соответствующем контексте) если .war залить в  /var/lib/tomcat7/webapps/.
Так уже можно работать, но для клиентов нужно спрятать tomcat за прокси, чтобы иметь возможность фровардить порты и прятать контексты за доменными именами. 

3. Nginx
wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key

В конец файла /etc/apt/sources.list добавляем строки:
deb http://nginx.org/packages/ubuntu/ codename nginx 
deb-src http://nginx.org/packages/ubuntu/ codename nginx

где codename - имя дистрибутива (посмотрите его в строчках выше или получите из сat /etc/*-release) 

sudo apt-get update
sudo apt-get install nginx

Это почти всё. Осталось только настроить проксирование запросов. Допустим, мы пишем конфиг для нашего хоста newgoogle.com, приложение для которого мы в tomcat-e развернули на контексте /ng:
sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/newgoogle.com.conf
sudo vim /etc/nginx/conf.d/newgoogle.com.conf
меняем server_name на newgoogle.com и меняем location-секцию на: 
    location / {
        proxy_pass        http://localhost:8080/ng/;
        proxy_set_header  X-Real-IP  $remote_addr;
    }

P.s.: Ничего сверхестественного тут нет, любой админ сделает вам это одним пальцем с закрытыми глазами после восьми банок пива. Но зачем его беспокоить? То, что описано тут - достаточно тривиально, и сделав это однажды просто повторяйте всегда при необходимости точно также. Пусть гуру-админы занимаются тюнингом наших серверов и тонкой настройкой сетевого трафика. Простые вещи делаем сами.

среда, 9 октября 2013 г.

Авторизация в web-приложениях. Заменяем Spring Security на 100 строк своего кода :)

Есть несколько принципов разработки приложений, вроде бы верных и абстрактно полезных. Эти принципы настолько укоренились в сознании современного программиста, что порой заменяют ему здравый смысл, лишают его способности, которая так ценна в его профессии: способности думать. Вот к примеру:
"Нельзя делать свои велосипеды! Надо использовать проверенные и отлаженные библиотеки!"
Вроде бы верно. Но если посмотреть чуть глубже... Почему так? Адепты "проверенных библиотек" с горящими глазами скажут что-то вроде: "Свои велосипеды писать дольше, их надо отлаживать и их труднее сопровождать". Для "сферического проекта в вакууме" это верно. Но давайте рассмотрим задачу реальную. Я постараюсь поставить условия задачи, которые чаще всего встречались в моей, уже почти 10-летней практике. И покажу решение, которое писать быстрее, проще и легче сопровождать, хотя оно и является классическим "велосипедом" :)

пятница, 4 октября 2013 г.

Создаём свои всплыващие сообщения в Android

Если вы хотите реализовать свои собственные всплывающие сообщения, которыми можно управлять, то этот пост точно для вас! Вы спросите, а почему не взять уже готовую реализацию Toast, и все дела?  Ответ следующий, да спору нет в эффективности этого класса, но я столкнулся с тем, что мне нужны были такие всплывающие сообщения, для которых можно было бы установить, время жизни, анимацию или например обработать событие по нажатию кнопки, которая размещена в вашем контенте. Вот для этих целей, был и создан класс, который может выполнить все перечисленные возможности в отличии о Toast.

среда, 2 октября 2013 г.

Сериализация и десериализация объектов

Программисты пишут код для того, чтобы решить какую-то задачу. Тот или другой способ они выбирают в зависимости от проблем, которые приходится преодолевать на пути к решению. Чаще всего проблемы видны сразу:

  • "...мы планируем обслуживать 10 миллионов клиентов к концу первого года." - Пишем с оглядкой на масштабирование.
  • "...нужно быстро внедрять 100500 похожих фич." - Думаем над системой плагинов.
  • И т.п. 
Однако есть проблемы, которые вроде как и не заметны поначалу. Но потом, спустя год активной доработки проекта мы вдруг увязаем в них как в болоте.  Одну такую я встретил недавно и цепочка моих рассуждений при её решении показалась мне достаточно интересной, чтобы оформить её тут. В конце этой цепочки - довольно элегантное на мой взгляд решение, которое позволит реализовать специфическую сериализацию объектов в вашем проекте. Это не открытие, подобный подход используется, например, в Parcelable в Android. Но паттерн, до которого дошёл "от проблемы" а не путём изучения научных трудов, запоминается намного лучше.
И, кстати, если вы пришли сюда скопировать исходничек не вникая в текст, лучше почитайте другие посты. Тут много букв, картинок и вообще, скукотища ;)

пятница, 7 июня 2013 г.

Делаем собственный компонент для Android-приложений

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

пятница, 26 апреля 2013 г.

Обучаем Tesseract

Tesseract - свободная платформа для оптического распознавания текста, исходники которой Google подарил сообществу в 2006 году. Если вы пишите софт для распознавания текста, то вам наверняка приходилось обращаться к услугам этой мощной библиотеки. И если она не справилась с вашим текстом, то выход у вас остаётся один - научить её. Процесс этот достаточно сложный и изобилует не очевидными а порой и прям-таки магическими действиями. Оригинальное описание есть тут. Мне понадобился почти целый день на постижение всей его глубины, поэтому тут я хочу сохранить, надеюсь, более понятный его вариант. Так чтобы помочь себе и другим пройти этот путь в следующий раз быстрее.

понедельник, 15 апреля 2013 г.

Шифрование с открытым ключом в java

Давным-давно я как-то описывал несколько примеров использования различных алгоритмов шифрования в java. Решил вот продолжить полезную тему и описать простой пример реализации шифрования алгоритмом RSA.
В отличие от описанных ранее, этот алгоритм асимметричный: т.е. данные шифруются одним ключом (публичным) а расшифровываются другим (приватным).
Есть много преимуществ такого алгоритма перед симметричными, и одно из главных - нем не нужен защищённый канал для передачи секретного ключа. Публичный ключ из своей пары мы можем разместить где угодно ни о чём не беспокоясь: этим ключом расшифровать адресованные нам сообщения в принципе невозможно. И наоборот, наш "собеседник" также свободно распространяет свой публичный ключ и мы можем передавать ему свои сообщения не опасаясь, что их прочитает кто-нибудь посторонний.
Ключи для обмена данными обычно генерируются один раз. После генерации их можно сохранить в файлы. При установке соединения с кем-то мы сохраняем его публичный ключ и передаём ему свой. Если получено шифрованное сообщение, мы восстанавливаем свой приватный ключ и с его помощью выполняем расшифровку.

пятница, 1 февраля 2013 г.

Создание websocket-приложения на Tomcat 7

Ранее я писал о своём опыте использования технологии websocket в связывании серверного java-приложения и android-клиента. Я предлагал использовать jetty 8 и делал обычное консольное java-приложение. Но если у нас websocket является частью web-приложения с сервлетами и т.п., то такой подход уже не годится. Да и зачем тянуть в свой проект jetty, если наш старый добрый Tomcat с версии 7.0.27 уже поддерживает websocket самостоятельно?
Сегодня мы сделаем websocket-приложение с помощью одного только Tomcat и javascript на клиенте.

WebSocketServlet
Ключевым механизмом реализации websocket в Tomcat является класс org.apache.catalina.websocket.WebSocketServlet. Он расширяет обычный javax.servlet.http.HttpServlet а значит мы можем "мапить" его на url как это делаем с остальными своими сервлетами. Собственно сама наша реализация крайне проста:

import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
 
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
 
@WebServlet(name = "WsServlet", urlPatterns = {"/ws"})
public class WsServlet extends WebSocketServlet {
    @Override
    protected StreamInbound createWebSocketInbound(String s, HttpServletRequest httpServletRequest) {
        return new WsConnection();
    }
}

Мы тут реализуем метод, который возвращает объект, отвечающий за обслуживание websocket-соединения. Сам объект WsConnection выглядит так:

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.WsOutbound;
 
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.concurrent.ArrayBlockingQueue;
 
public class WsConnection extends MessageInbound {
 
    public static ArrayBlockingQueue<WsOutbound> connections = new ArrayBlockingQueue<WsOutbound>(100);
    private WsOutbound outbound;
 
    @Override
    protected void onBinaryMessage(ByteBuffer byteBuffer) throws IOException {
    }
 
    @Override
    protected void onTextMessage(CharBuffer charBuffer) throws IOException {
        broadcast(charBuffer.toString());
    }
 
    @Override
    protected void onOpen(WsOutbound outbound) {
        this.outbound = outbound;
        connections.add(outbound);
    }
 
    @Override
    protected void onClose(int status) {
        connections.remove(this.outbound);
    }
 
    private void broadcast(String message) {
        for (WsOutbound connection : connections) {
            try {
                CharBuffer buffer = CharBuffer.wrap(message);
                connection.writeTextMessage(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Тут мы расширяем MessageInbound, что позволяет нам переопределяя соответствующие методы обрабатывать события установления и обрыва соединения, получения сообщения и т.п. При подключении нового клиента мы добавляем его в синхронизированную очередь, при отключении - удаляем. При получении сообщения просто рассылаем его всем подключенным клиентам. Такой вот чатик :)
На клиентской стороне можно сделать много красивостей, но мы обойдёмся минимальным количеством кода. Только чтобы убедиться в работоспособности сервера.

<html>
  <head>
    <title></title>
      <script type="text/javascript">
          var socket = new WebSocket("ws://mydomain.com:8080/ws");
 
          socket.onopen = function() {
              alert("Соединение установлено.");
          };
 
          socket.onclose = function(event) {
              if (event.wasClean) {
                  alert('Соединение закрыто');
              } else {
                  alert('Обрыв соединения');
              }
              alert('Код: ' + event.code + ' причина: ' + event.reason);
          };
          socket.onmessage = function(event) {
              var logarea = document.getElementById("log");
              logarea.value = event.data+"n"+logarea.value;
          };
          socket.onerror = function(error) {
              alert("Ошибка " + error.message);
          };
 
          function send() {
              var s = document.getElementById("in").value;
              socket.send(s);
          }
 
      </script>
  </head>
  <body>
    <input type="text" id="in" /><input type="button" onclick="send()" value="send" />
    <br/>
    <textarea id="log" rows="8" cols="20">

  

среда, 2 января 2013 г.

Устанавливаем sun java в Linux Mint 14

В большинстве Linux-дистрибутивов первое, что мне приходится делать после установки - заменять OpenJDK на "нормальную" java. Не то чтобы я был против лицензионно-чистого софта, просто большинство моих инструментов написаны так, что с OpenJDK ведут себя странно. Обычно процесс замены java на моей машине достаточно однообразен и нетехнологичен: скачать, распаковать, сделать update-alternatives. Но сегодня я таки решил сделать всё правильно:

sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java6-installer 

Инсталлятор сам обновляет alternatives, так что больше ни о чём заботиться не нужно. OpenJDK в системе остаётся, но по умолчанию используется теперь именно java от Oracle. Из этого же репозитория, само собой, можно поставить и 7 и 8 JDK, если они вам больше нравятся.