Общее·количество·просмотров·страницы

Java Dev Notes - разработка на Java (а также на JavaScript/Python/Flex и др), факты, события из АйТи

понедельник, 13 сентября 2010 г.

Блокировка чтения/записи. Часть 2.

Рассмотрим повторное блокирование (reentrant locking) для читающих потоков.

По материалам Read/Write Locks in Java - Read Reentrance.

Рассмотрим ситуацию:
  • Поток 1 получил доступ на чтение
  • Поток 2 запросил доступ на запись (изменение)
  • Поток 1 повторно запросил получил доступ на чтение, но он окажется заблокированным, т.к. уже имеется запрос на изменение данных от Потока 2

В этом случае мы столкнемся с вариантом дедлока - Поток 2 окажется заблокированным, т.к. уже имеется читающий поток, а Поток 1 будет заблокирован, т.к. есть запрос на изменение данных от Потока 2.

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

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

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

Код:


public class ReadWriteLock {
private Map<Thread, Integer> readingThreads = new HashMap<Thread, Integer>();
private int writers = 0;
private int writeRequests = 0;
 
public synchronized void lockRead() throws InterruptedException {
Thread callingThread = Thread.currentThread();
while(!canGrantReadAccess(callingThread)){
wait();
}
int accessCount = getReadAccessCount(callingThread);
readingThreads.put(callingThread,accessCount+1);
}
 
public synchronized void unlockRead() {
Thread callingThread = Thread.currentThread();
int accessCount = getReadAccessCount(callingThread);
if(accessCount == 1) {
readingThreads.remove(callingThread);
} else {
readingThreads.put(callingThread, accessCount-1);
}
notifyAll();
}
 
private boolean canGrantReadAccess(Thread callingThread) {
if(writers > 0) return false;
if(isReader(callingThread) return true;
if(writeRequests > 0) return false;
return true;
}
 
private int getReadAccessCount(Thread callingThread) {
Integer accessCount = readingThreads.get(callingThread);
if(accessCount == null) return 0;
return accessCount.intValue();
}
 
private boolean isReader(Thread callingThread){
return readingThreads.get(callingThread) != null;
}
 
public synchronized void lockWrite() throws InterruptedException{
writeRequests++;
 
while(readingThreads.size() > 0 || writers > 0){
wait();
}
writeRequests--;
writers++;
}
 
public synchronized void unlockWrite() {
writers--;
notifyAll();
}
}

четверг, 26 августа 2010 г.

Блокировка чтения/записи. Часть 1.

Read/write Locks

По материалам Read/Write Locks in Java

Условия:

Имеются общие данные, к которым потоки обращаются на чтение и изменение (запись). При этом изменение данных происходит относительно редко. Гораздо чаще происходит их чтение.

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

Задача:

Требуется эффективно синхронизировать читающие и изменяющие потоки между собой.

Решение:

Для начала, еще раз подытожим условия блокировки:

Доступ для чтения: если нет потоков, которые изменяют данные, а также нет потоков, которые запросили доступ для изменения данных.

Доступ для записи: если нет потоков, которые читают или изменяют данные.

Если поток хочет прочитать данные, то это позволяется, если ни один поток не изменяет данные и ни один поток не запросил доступ на запись данных. Мы отдаем записи данных более высокий приоритет, чем чтению. Если бы мы так не делали, то могло бы возникнуть "голодание" (starvation) потоков, изменяющих данные (при условии, что запросы на чтение данных происходят чаще, чем запросы на запись данных). Потоки, запрашивающие доступ на запись, должны были бы ждать, пока не отработают потоки, читающие данные. Так как запросов на чтение много, то новые читающие потоки добавлялись бы постоянно, что привело бы в долговременному (а то и вообще бесконечному) простою потоков на запись. Поэтому поток может получить разрешение на чтение, только если ни один поток не изменяет данные и ни один поток не запросил доступ на запись.

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

Поэтому мы можем написать следующие код:

public class ReadWriteLock{
 
private int readers = 0;
private int writers = 0;
private int writeRequests = 0;
 
public synchronized void lockRead() throws InterruptedException{
while(writers > 0 || writeRequests > 0){
wait();
}
readers++;
}
 
public synchronized void unlockRead(){
readers--;
notifyAll();
}
 
public synchronized void lockWrite() throws InterruptedException{
writeRequests++;
 
while(readers > 0 || writers > 0){
wait();
}
writeRequests--;
writers++;
}
 
public synchronized void unlockWrite() {
writers--;
notifyAll();
}
}


Здесь мы имеем два метода для начала блокировки, и два метода для окончания блокировки. Код, который мы реализовали, не подразумевает возвратного блокирования (reentrant locking).

Ниже приведен пример использования блокировки чтения/записи для потоко-безопасного кеша:

public class Cache {
HashMap<Integer,String> map = new HashMap<Integer,String>();
ReadWriteLock lock = new ReadWriteLock();
 
public String get(Integer key) throws CacheException {
try {
lock.lockRead();
return map.get(key);
} catch (InterruptedException ie) {
throw new CacheException(ie);
} finally {
lock.unlockRead();
}
}
 
public void put(Integer key, String value) throws CacheException {
try {
lock.lockWrite();
map.put(key,value);
} catch (InterruptedException ie) {
throw new CacheException(ie);
} finally {
lock.unlockWrite();
}
}
 
public void remove(Integer key) throws CacheException {
try {
lock.lockWrite();
map.remove(key);
} catch (InterruptedException ie) {
throw new CacheException(ie);
} finally {
lock.unlockWrite();
}
}
}

вторник, 24 августа 2010 г.

Многопоточный сервер

Рассмотрим создание многопоточного сервера. Наш сервер будет уметь немногое: для каждого нового соединения будет создаваться отдельный поток, который будет печатать фразу "jdevnotes multithreaded Server runs" и закрывать соединение. Естественно, что сервер будет слушать какой-либо порт - пусть это будет порт 9000.

Как нам завершать работу сервера? Для этого применим следующий прием: стоп-монитор. Стоп-монитор будет слушать отдельный порт (назовем его стоп-портом) - пусть это будет порт 9001. Если произошло соединение с этим портом и на стоп-монитор отправлена любая последовательность байтов, то стоп-монитор дает команду на прекращение работы. Естественно, что стоп-монитор работает в отдельном потоке.

Сервер будет состоять из 4 файлов:
Main.java - содержит метод main,
StopMonitor.java - содержит стоп-монитор,
MultiThreadedServer.java - основной поток сервера, запускает рабочие потоки, которые и пишут/читают данные для каждого соединения,
Worker.java - рабочий поток.

Код нашего метода main будет таков (Main.java):

public class Main {
public static final int PORT_WORK = 9000;
public static final int PORT_STOP = 9001;
 
public static void main(String[] args) {
MultiThreadedServer server = new MultiThreadedServer(PORT_WORK);
new Thread(server).start();
try {
Thread monitor = new StopMonitor(PORT_STOP);
monitor.start();
monitor.join();
System.out.println("Right after join.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Stopping Server");
server.stop();
}
}
 


Что здесь происходит: стартуем MultiThreadedServer, затем стартуем стоп-монитор и присоединяем (join) его к текущему потоку. Теперь текущий поток (т.е. код метода main) будет ждать завершения потока стоп-монитора. И строчка

System.out.println("Right after join.....");

выполнится только после того, как поток стоп-монитора завершится. А завершится он только тогда, когда будет соединение на стоп-порт и на стоп-порт будет отправлена какай-нибудь последовательность байтов.

Смотрим код стоп-монитора (StopMonitor.java):

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
 
public class StopMonitor extends Thread {
 
private ServerSocket serverSocket;
 
public StopMonitor(int port) {
setDaemon(true);
setName("StopMonitor");
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
 
@Override
public void run() {
System.out.println("stop monitor thread listening on: "+ serverSocket.getInetAddress()+":"+serverSocket.getLocalPort());
Socket socket;
try {
socket = serverSocket.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
reader.readLine();
System.out.println("stop signal received, stopping server");
socket.close();
serverSocket.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
 


Код MultiThreadedServer будет таков (MultiThreadedServer.java):

import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;
 
public class MultiThreadedServer implements Runnable{
 
protected int serverPort = 9000;
protected ServerSocket serverSocket = null;
protected boolean isStopped = false;
 
public MultiThreadedServer(int port){
this.serverPort = port;
}
 
@Override
public void run(){
openServerSocket();
while(! isStopped()){
Socket clientSocket = null;
try {
clientSocket = this.serverSocket.accept();
} catch (IOException e) {
if(isStopped()) {
System.out.println("Server Stopped.") ;
return;
}
throw new RuntimeException("Error accepting client connection", e);
}
new Thread(
new Worker(clientSocket)
).start();
}
System.out.println("Server Stopped.") ;
}
 
 
private synchronized boolean isStopped() {
return this.isStopped;
}
 
public synchronized void stop(){
this.isStopped = true;
try {
this.serverSocket.close();
} catch (IOException e) {
throw new RuntimeException("Error closing server", e);
}
}
 
private void openServerSocket() {
System.out.println("Opening server socket...");
try {
this.serverSocket = new ServerSocket(this.serverPort);
} catch (IOException e) {
throw new RuntimeException("Cannot open port " + this.serverPort, e);
}
}
 
}


Код рабочего потока (Worker.java):

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
 
 
public class Worker implements Runnable {
 
protected Socket clientSocket = null;
 
public Worker(Socket clientSocket) {
this.clientSocket = clientSocket;
}
 
@Override
public void run() {
try {
InputStream input = clientSocket.getInputStream();
OutputStream output = clientSocket.getOutputStream();
long time = System.currentTimeMillis();
output.write("jdevnotes multithreaded server runs\n".getBytes());
output.close();
input.close();
System.out.println("Request processed: " + time);
} catch (IOException e) {
e.printStackTrace();
}
}
}
 


Запустив наше приложение, подсоединимся телнетом на порт 9000 локалхоста. Получим ответ:


$ telnet localhost 9000
Trying ::1...
Connected to localhost.
Escape character is '^]'.
jdevnotes multithreaded server runs
Connection closed by foreign host.


Теперь опять же телнетом зайдем на порт 9001 и напишем любую фразу ("ааа"):

$ telnet localhost 9001
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
aaa
Connection closed by foreign host.


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

суббота, 31 июля 2010 г.

Настройка trac для отсылки email

Используем trac - нужно настроить отсылку сообщений по электронной почте при появлении изменений в каком-либо тикете. Подробные настройки даны на странице Email Notification of Ticket Changes в вики trac, здесь же я ограничусь описанием того, что нужно было сделать у нас в проекте.

1) Открываем файл /home/trac/project/conf/trac.ini
2) Вносим изменения:

[notification]
smtp_enabled = true
smtp_server = localhost
smtp_from = trac@example.com
smtp_replyto = project@projects.example.com
smtp_always_cc = ticketmaster@example.com, theboss+myproj@example.com


Остальные настройки закомментируем.

На этом все.

понедельник, 26 июля 2010 г.

Шпаргалка по MySQL

Небольшая шпаргалка по MySQL:

1) Просмотр имеющихся баз:

show databases;

2) Просмотр таблиц в БД:

show tables in dbname;

3) Просмотр информации о столбцах в таблице:

show [full] columns in tablename;

4) Просмотр информации о кодировках:

show character set;

5) Создание БД с кодировкой UTF-8:

create database mydb DEFAULT CHARACTER SET utf8 default COLLATE utf8_general_ci ;

воскресенье, 25 июля 2010 г.

Итерация по Enumeration

Что делать, если нужно пробежаться по java.util.Enumeration? Мне это понадобилось, когда я писал отладочный метод, выводящий все заголовки HTTP-запроса. Метод getHeaderNames() из класса javax.servlet.http.HttpServletRequest возвращает Enumeration.

Можно конечно сделать по старинке, используя hasMoreElements()/nextElement():

Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String)headerNames.nextElement();
// do smth...
}


но так делать не очень-то и хотелось. Вместо этого превратим Enumeration в java.util.ArrayList и уже
по нему будем совершать итерации:

List<String> headerNames = Collections.list((Enumeration<String>)request.getHeaderNames());
for (String headerName : headerNames) {
String headerValue = request.getHeader(headerName);
// do smth....
}


Ну, и напоследок, код метода, который выводит в строку все заголовки HTTP-запроса:

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Collections;
import java.util.Enumeraion;
 
public static String httpHeadersToString(HttpServletRequest request) {
List<String> headerNames = Collections.list((Enumeration<String>)request.getHeaderNames());
StringBuilder sb = new StringBuilder();
for (String headerName : headerNames) {
String headerValue = request.getHeader(headerName);
sb.append(headerName).append("='").append(headerValue).append("'\n");
}
return sb.toString();
}

пятница, 9 июля 2010 г.

Подключение SSL к Томкату

Допустим, что нам в веб-приложении нужно иметь несколько URL, которые доступны только по HTTPS. Это может быть работа с любыми чувствительными данными: платежи, почта, и т. п. В основном, это, конечно, финансовые данные. Вот об этом и будет рассказано в этом посте.

По мотивам Setting Up SSL on Tomcat In 3 Easy Steps

Нужно сделать следующее:

  1. Сгенерировать ключ

  2. Настроить Tomcat для использования этого ключа

  3. Настроить веб-приложение на работу по HTTPS

Генерация ключа



Используем утилиту keytool:

keytool -genkey -alias jdevnotes -keypass jdevpasswd -keystore jdevnotes.keystore.bin -storepass jdevpasswd

Эта команда генерирует ключ с алиасом jdevnotes, который сохраняется в файл (keystore, хранилище ключей) jdevnotes.keystore.bin. Также задается пароль на ключ и на хранилище в целом (пароли одинаковые - jdevpasswd).

Немножко подробнее о ключах: Все ключи хранятся в хранилище ключей (файл jdevnotes.keystore.bin). Каждое хранилище может содержать множество ключей. Доступ к ключу осуществляется по алиасу. Алиасы - регистро-независимые, т.е. алиас john и JoHn - один и тот же алиас с точки зрения хранилища ключей.

При создании ключа задается также следующая информация:

What is your first and last name?
What is the name of your organizational unit?
What is the name of your organization?
What is the name of your City or Locality?
What is the name of your State or Province?
What is the two-letter country code for this unit?


Здесь вводится информация о том, кто ты такой и где находишься.

Настройка Tomcat для использования ключа



Перемещаем файлик с ключом в каталог $CATALINA_HOME/webapps. После этого редактируем файлик $CATALINA_HOME/conf/server.xml. В этом файле раскомментируем коннектор на 8443 порту, добавим ссылку на файл с ключами и пароль для хранилища, получим:

<Connector port=”8443″
maxThreads=”150″ minSpareThreads=”25″ maxSpareThreads=”75″
enableLookups=”true” disableUploadTimeout=”true”
acceptCount=”100″ debug=”0″ scheme=”https” secure=”true”
clientAuth=”false” sslProtocol=”TLS”
keystoreFile=”/webapps/jdevnotes.keystore.bin”
keystorePass=”jdevpasswd” />


Теперь зайдите по адресу

https://localhost:8443

мы зашли на менеджер Томката по HTTPS. Теперь, любое приложение, которое развернуто на Томкате, может быть доступно как по HTTP, так и по HTTPS. Теперь рассмотрим, как сделать так, чтобы некоторые урлы были доступны только через HTTPS.

Настройка веб-приложения на работу только по HTTPS



Откроем файлик web.xml нашего приложения и добавим туда следующий код (код нужно помещать в самом конце контейнера web-app):

<security-constraint>
<web-resource-collection>
<web-resource-name>securedapp</web-resource-name>
<url-pattern>/billing/fullBalanceHistory</url-pattern>
<url-pattern>/mail/inbox</url-pattern>
<url-pattern>/somethingelse/*</url-pattern>
</web-resource-collection>
 
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>


Следующие ссылки будут доступны только по HTTPS:
/billing/fullBalanceHistory
/mail/inbox
/somethingelse/*

Если мы хотим, чтобы все приложение было доступно по HTTPS, то просто ставим паттерн /*. Если хотим вдруг выключить SSL, то для этого можно просто записать NONE в тег
<transport-guarantee>

воскресенье, 4 июля 2010 г.

Генератор паролей на Питоне

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

import random
 
passwordSymbols = ";*()_+=-,.[]{}1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 
def genRandomSymbols(len):
"""
:param len: length of random symbols sequence
:raise BadValueError: If len < 0
:return: The random sequence of len length.
"""

if len < 0:
raise ValueError("len should be greater or equal than 0")
elif len == 0:
return ""
else:
result = ""
for i in range(len):
result = result + random.choice(passwordSymbols)
return result
 
def main():
print genRandomSymbols(12)
 
if __name__ == "__main__":
main()

четверг, 1 июля 2010 г.

Вышел релиз Google App Engine SDK 1.3.5

Вышел релиз Google App Engine SDK 1.3.5. Release notes: http://code.google.com/p/googleappengine/wiki/SdkReleaseNotes

Новое:

  • Можно включить прекомпиляцию Питона

  • Конфигурирование лимита хранения Task Queue

  • Task Queues теперь могут иметь до 50 задач на очередь (раньше - 50 задач на приложение)

  • Программный доступ к блобам используя BlobReader - файлоподобный интерфейс для чтения блобов

  • Улучшения в BulkLoader

  • Команды remote_api_shell могут посылаться через HTTPS или HTTP.

  • Admin Console логгирует теперь время задержки запросов (request time latency).

  • Db.delete теперь принимает коллекции

  • Хранилище поддерживает концевые курсоры (end cursors)

  • Исправлено несколько багов

10 Excellent Feedback Tools for Web Designers

Как создателю сайта получить фидбек от пользователей.
10 Excellent Feedback Tools for Web Designers

среда, 23 июня 2010 г.

Запуск Trac Standalone и аутентификация пользователей

Мы хотим запустить trac. обычно его запускают из-под Apache, но мы не будем этого делать. Вместо этого мы запустим Trac Standalone - самостоятельный веб-сервер, который и будет хостить trac. На странице проекта Trac Standalone можно подробнее прочитать об этом способе запуска.

Считаем, что trac у нас уже установлен.

Запустим trac на 9990 порту на проекте prj1:

$ tracd --port 9990 /home/trac/prj1

Отлично, tracd стартовал.

Теперь подумаем про аутентификацию пользователей.

Вначале создаем даем двум пользователям, vasya и petya, привилегии админов trac:

$ trac-admin /home/trac/prj1 permission add vasya TRAC_ADMIN
$ trac-admin /home/trac/prj1 permission add petya TRAC_ADMIN


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

username:realm:md5sum(username:realm:password)

Например, если пользователь petya имеет пароль password1, а realm мы задали как 'trac', то строка для petya в файле будет такой:

petya:trac:7f9c7f2ecd9894a028ce8c10ede46719

где 7f9c7f2ecd9894a028ce8c10ede46719 - md5-хешсумма от строки 'petya:trac:password1'.

Пусть мы создали этот файл для двух пользователей (пароль для vasya: 'password2'):

petya:trac:7f9c7f2ecd9894a028ce8c10ede46719
vasya:trac:1bacebce4824deae3bb3c9851c22fbbc


Теперь этот файл (trac_users.txt) можно указать в настройках tracd:

$ tracd --port 9990 --auth=prj1,trac_users.txt,trac /home/trac/prj1

На этом все.

воскресенье, 20 июня 2010 г.

Назначение и изменение прав на каталоги и файлы в unix

Назначение и изменение прав на каталоги и файлы в unix:
Стандартные права Unix, SUID, SGID, Sticky биты

Хорошее, годное и подробное руководство.

вторник, 15 июня 2010 г.

Связка nginx - Tomcat: прокидывание реального IP пользователя до Томката

При разработке Evaphone мы решили все томкаты проксировать с помощью nginx. Это хорошо известная схема - frontend server - backend server. Frontend сервер (в нашем случае - nginx) занимается отдачей статики, а на backend он проксирует запросы на динамику. Такая схема работы позволяет снизить нагрузку на "тяжелый" бэкенд и увеличить производительность приложения.

Внутри нашего приложения используется информация о пользователи, а именно, IP-адрес. Когда запрос проксируется nginx'ом, то, естественно, реальный IP-адрес пользователя подменяется адресом машины, на которой работает nginx. Однако, nginx не "теряет" реальный IP-адрес. Вместо этого он передает его в HTTP-заголовке "x-real-ip". Это настраивается следующим конфигом nginx:


location = /tomcat {
proxy_pass ___tomcat_app_url___;
proxy_redirect off;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}


В этот конфиг вместо ___tomcat_app_url___ нужно подставить URL приложения на томкате, куда будет передаваться запрос. Например, может быть следующий URL: http://127.0.0.1:8080/myapp (это если tomcat и nginx работают на одной машине). Этот конфиг говорит томкату, что все запросы по адресу <ваш_ip>/tomcat нужно проксировать на ___tomcat_app_url___.

Теперь рассмотрим настройку Tomcat. Мы знаем, что реальный айпишник приходит к нам в заголовке запроса "x-real-ip". Подмену адреса (вернее, вытягивание реального айпишника) мы делаем с помощью сервлетного фильтра:

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.FilterConfig;
import javax.servlet.FilterChain;
import javax.servlet.ServletResponse;
import javax.servlet.ServletRequest;
import javax.servlet.Filter;
 
public class GetRealIPFilter implements Filter {
 
public static final String X_FORWARDED_FOR = "x-forwarded-for";
public static final String X_REAL_IP = "x-real-ip";
 
public GetRealIPFilter() {
//System.out.println("[GetRealIPFilter.GetRealIPFilter]");
}
 
public void destroy() {
//System.out.println("[GetRealIPFilter.destroy]");
}
 
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException
{
//System.out.println("[GetRealIPFilter.doFilter]");
if (request instanceof HttpServletRequest) {
 
HttpServletRequest httpRequest = (HttpServletRequest)request;
 
final String realIp = httpRequest.getHeader(X_REAL_IP);
final String realHost = httpRequest.getHeader(X_FORWARDED_FOR);
 
//System.out.println("[GetRealIPFilter.doFilter], realIp='"+realIp+"'");
//System.out.println("[GetRealIPFilter.doFilter], realHost='"+realHost+"'");
 
if (realIp != null) {
//System.out.println("[GetRealIPFilter.doFilter], realIp != null, wrap request");
 
filterChain.doFilter(
new HttpServletRequestWrapper(httpRequest) {
public String getRemoteAddr() {
return realIp;
}
public String getRemoteHost() {
return realHost;
}
},
response
);
return;
}
}
filterChain.doFilter(request, response);
}
 
public void init(FilterConfig filterConfig) throws ServletException {
//System.out.println("[GetRealIPFilter.init]");
}
}


Код достаточно понятный. Что мы делаем:

  1. Проверяем, есть ли заголовок "x-real-ip". Если его нет, то ничего не делаем, т.е. все остается по-старому. Tomcat продолжит принимать запросы напрямую

  2. Если заголовок "x-real-ip" присутствует, то в цепочку фильтров мы передаем не пришедший к нам от nginx request, а его обертку HttpServletRequestWrapper, в которой мы подменили методы getRemoteAddr и getRemoteHost так, чтобы они выдавали данные, пришедшие в заголовках "x-real-ip", "x-forwarded-for" соответственно.


Осталось только замэппить наш фильтр на все приложение. В файле web.xml добавляем следующие строки:

<filter>
<filter-name>get_real_ip_filter</filter-name>
<filter-class>GetRealIPFilter</filter-class>
</filter>
 
<filter-mapping>
<filter-name>get_real_ip_filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


На этом все!

Пара ссылок:

Настройка nginx для работы с apache и tomcat серверами на примере Ubuntu 10.04 Server

Nginx + Tomcat - тред на форуме nginx

JSP+Tomcat+nginx - тред на форуме RSDN

UPD 11 Июля 2010: Улучшенный код:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) 
throws ServletException, IOException
{
//System.out.println("[GetRealIPFilter.doFilter]");
if (request instanceof HttpServletRequest) {
 
HttpServletRequest httpRequest = (HttpServletRequest)request;
 
final String realIp = httpRequest.getHeader(X_REAL_IP);
final String realHost = httpRequest.getHeader(X_FORWARDED_FOR);
 
//System.out.println("[GetRealIPFilter.doFilter], realIp='"+realIp+"'");
//System.out.println("[GetRealIPFilter.doFilter], realHost='"+realHost+"'");
 
if (realIp != null) {
if (realHost != null) {
filterChain.doFilter(new HttpServletRequestWrapper(httpRequest) {
public String getRemoteAddr() {return realIp;}
public String getRemoteHost() {return realHost;}
},response);
return;
} else {
filterChain.doFilter(new HttpServletRequestWrapper(httpRequest) {
public String getRemoteAddr() {return realIp;}
},response);
return;
}
} else if (realHost != null) {
filterChain.doFilter(new HttpServletRequestWrapper(httpRequest) {
public String getRemoteHost() {return realHost;}
},response);
return;
}
}
filterChain.doFilter(request, response);
}

суббота, 12 июня 2010 г.

Установка yum на CentOS 5.4 (Final):

Ставим yum на CentOS 5.4 (Final):


cd ~/
mkdir temp
cd temp
wget http://mirror.centos.org/centos/5/os/i386/CentOS/yum-3.2.22-26.el5.centos.noarch.rpm
wget http://mirror.centos.org/centos/5/os/i386/CentOS/python-iniparse-0.2.3-4.el5.noarch.rpm
wget http://mirror.centos.org/centos/5/os/i386/CentOS/rpm-python-4.4.2.3-18.el5.i386.rpm
wget http://mirror.centos.org/centos/5/os/i386/CentOS/python-urlgrabber-3.1.0-5.el5.noarch.rpm
wget http://mirror.centos.org/centos/5/os/i386/CentOS/yum-fastestmirror-1.1.16-14.el5.centos.1.noarch.rpm
wget http://mirror.centos.org/centos/5/os/i386/CentOS/yum-metadata-parser-1.1.2-3.el5.centos.i386.rpm
wget http://mirror.centos.org/centos/5/os/i386/CentOS/m2crypto-0.16-6.el5.6.i386.rpm
rpm -ivh *rpm


список RPM-пакетов для CentOS можно посмотреть по ссылке http://mirror.centos.org/centos/5/os/i386/CentOS

Здесь http://www.electrictoolbox.com/install-yum-with-rpm-on-centos/ раскрывается похожая тема

вторник, 8 июня 2010 г.

Установка переменных среды для Java, Apache Tomcat

Устанавливаем переменные среды JAVA_HOME, CATALINA_HOME. Только что столкнулся с тем, что если устанавливать переменные среды через export из SSH, то они не сохраняются между SSH-сессиями. В этом случае их надо прописать в файле ~/bashrc или ~/bash_profile.

Прописал следующее, теперь все работает:

Добавил следующие строки в файл ~/bashrc

JAVA_HOME=/usr/java/jdk1.6.0_20
export JAVA_HOME

CATALINA_HOME=/usr/java/apache-tomcat-6.0.26
export CATALINA_HOME

PATH=$PATH:$JAVA_HOME/bin

export PATH


Весь же файл ~/bashrc выглядит так


# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi

JAVA_HOME=/usr/java/jdk1.6.0_20
export JAVA_HOME

CATALINA_HOME=/usr/java/apache-tomcat-6.0.26
export CATALINA_HOME

PATH=$PATH:$JAVA_HOME/bin

export PATH

Установка и удаление java на CentOS

Устанавливаем JDK на CentOS:


  1. Скачиваем jdk-6u20-linux-i586.bin с сайта http://java.sun.com

  2. Заливаем его на сервер в каталог /usr/java

  3. Меняем права $>chmod 755 jdk-6u20-linux-i586.bin

  4. Выполняем #./jdk-6u20-linux-i586.bin

  5. Набираем yes для согласия с лицензией, далее идет установка

  6. Устанавливаем переменную среды # export JAVA_HOME=/usr/java/jdk1.6.0_20
    Затем кладем ее в PATH:
    # export PATH=$PATH:$JAVA_HOME/bin

  7. Проверочка #java -version
    Получаем:

    # java -version
    java version "1.6.0_20"
    Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
    Java HotSpot(TM) Client VM (build 16.3-b01, mixed mode, sharing)




UPDATE:

Как удалять JRE

Вот ссылка на java.com: How do I uninstall Java for Linux ?

Если JRE была поставлена из RPM:

1) проверяем, поставлена ли JRE из rpm:

rpm -qa | grep jre

2) удаляем

rpm -e jre-1.5.0_21-fcs

3) Профит! (иногда надо перезагрузить машину)

Постоянные читатели