Аналог cookeis в spring remoting

В браузере есть способ хранить и передавать некоторую дополнительную информацию, которая не присутствует в параметрах при вызове GET или не хранится в теле запроса POST. Данная функциональность реализована с помощью кук.
Для браузера это позволяет создать некоторое постоянное хранилище данных на стороне клиента, с помощью которых сервер может идентифицировать клиента.
Spring Remoting можно реализовать поверх протокола http, однако ни одного упоминания про куки я не нашел. Попробуем реализовать их самостоятельно.

Пусть есть некий сервис, опубликованный с помощью spring remoting:

package foo;
public interface FooService {
   public String hello(String name);
}



package foo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class FooServiceImpl {
   private Log logger = LogFactory.getLog(getClass());
   public String hello(String name) {
      logger.info("Greeting "+name);
      return "Hello "+name;
   }
}



<bean id="fooService" class="foo.FooServiceImpl">

<bean name="/FooService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
   <property name="service" ref="fooService"/>
   <property name="serviceInterface" value="foo.FooService"/>
</bean>


На клиенте мы получаем ссылку на сервис:

<bean id="fooService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
   <property name="serviceUrl" value="http://${server.address}:${server.port}/server/remoting/FooService"/>
   <property name="serviceInterface" value="foo.FooService"/>
</bean>


Для начала реализуем способ хранения кук на клиенте. Так как клиент — swing-приложение, то в качестве хранителя можно использовать обычный синглтон. Все куки будем хранить в карте, ключ — имя куки.

package foo;

import java.util.*;
import java.io.Serializable;

public class CookiesHolder {

    private Map<String, Serializable> cookies;

    public void addCookie(String name, Serializable o) {
        if (cookies == null) {
            cookies =new HashMap<String, Serializable>();
        }
        cookies.put(name, o);
    }

    public Serializable getCookie(String name) {
        return cookies!=null ? cookies.get(name) : null;
    }

    public Set<String> getCookiesNames() {
        if (cookies==null) {
            return Collections.emptySet();
        }
        else {
            return cookies.keySet();
        }
    }
}



<bean id="cookieHolder" class="core.service.remote.CookiesHolder" />



Далее необходим способ передать куки на сервер при вызове любого метода. Изучая класс org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean, я наткнулся на метод 

/**
 * Set the RemoteInvocationFactory to use for this accessor.
 * Default is a {@link DefaultRemoteInvocationFactory}.
 * <p>A custom invocation factory can add further context information
 * to the invocation, for example user credentials.
 */
public void setRemoteInvocationFactory(RemoteInvocationFactory remoteInvocationFactory) {
   this.remoteInvocationFactory =
    (remoteInvocationFactory != null ? remoteInvocationFactory : new DefaultRemoteInvocationFactory());
}


По описанию выглядит как то, что нам нужно. Если изучить код
RemoteInvocationFactory, мы увидим единственный метод:

RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation);

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

private Map attributes;

смотрим описание:
/**
* Add an additional invocation attribute. Useful to add additional
* invocation context without having to subclass RemoteInvocation
* Attribute keys have to be unique, and no overriding of existing
* attributes is allowed.
* The implementation avoids to unnecessarily create the attributes
* Map, to minimize serialization size.
* @param key the attribute key
* @param value the attribute value
* @throws IllegalStateException if the key is already bound
*/
public void addAttribute(String key, Serializable value) throws IllegalStateException {


Итак, при вызове метода сервиса, есть возможность, переопределив RemoteInvocationFactory, поместить в RemoteInvocation дополнительные атрибуты, в частности, наши куки:

package foo;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.remoting.support.DefaultRemoteInvocationFactory;
import org.springframework.remoting.support.RemoteInvocation;

import java.io.Serializable;

public class CookiesBasedRemoteInvocationFactory extends DefaultRemoteInvocationFactory {
   
    private CookiesHolder cookiesHolder;
    @Override
    public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {
        final RemoteInvocation invocation = super.createRemoteInvocation(methodInvocation);
        final Application instance = Application.getInstance();
        CookiesHolder holder;
        if (instance!=null) {
            holder = instance.getCookiesHolder();
        }
        else {
            holder = new CookiesHolder();            
        }
        for (String name : holder.getCookiesNames()) {
            final Object value = holder.getCookie(name);
            if (value instanceof Serializable) {
                invocation.addAttribute(name, (Serializable) value);
            }
        }
        return invocation;
    }

    public void setCookiesHolder(CookiesHolder cookiesHolder) {
        this.cookiesHolder = cookiesHolder;
    }

}



теперь изменим конфигурацию ссылки на сервис:
<bean id='cookiesBasedRemoteInvocationFactory' class="foo.CookiesBasedRemoteInvocationFactory"/>

<bean id="fooService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
   <property name="serviceUrl" value="http://${server.address}:${server.port}/server/remoting/FooService"/>
   <property name="serviceInterface" value="foo.FooService"/>
   <property name="remoteInvocationFactory" ref="cookiesBasedRemoteInvocationFactory"/>
</bean>


Таким образом, любой вызов метода сервиса (в частности hello()) будет передавать на сервер наши куки. Осталось их получить на сервере. Будем действовать аналогичным способом.

Создаем структуру для хранения кук:

package foo;

import org.springframework.util.Assert;

public class ServerCookiesHolder {

    private static ThreadLocal contextHolder = new ThreadLocal();

    public static void clearContext() {
        contextHolder.set(null);
    }

    public static CookiesHolder getContext() {
        if (contextHolder.get() == null) {
            contextHolder.set(new CookiesHolder());
        }

        return (CookiesHolder) contextHolder.get();
    }

    public static void setContext(CookiesHolder context) {
        Assert.notNull(context, "Only non-null CookiesHolder instances are permitted");
        contextHolder.set(context);
    }

}



Изучаем HttpInvokerServiceExporter, находим метод setRemoteInvocationExecutor, который устанавливает обработчика вызова метода. Переопределяем обработчик:

package foo;

import org.springframework.remoting.support.DefaultRemoteInvocationExecutor;
import org.springframework.remoting.support.RemoteInvocation;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Set;

public class CookiesBasedRemoteInvocationExecutor extends DefaultRemoteInvocationExecutor {

    @Override
    public Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        final Map map = invocation.getAttributes();
        if (map!=null) {

            final Set<String> set = map.keySet();
            final CookiesHolder cookiesContext = ServerCookiesHolder.getContext();
            for (String name : set) {
                cookiesContext.addCookie(name, invocation.getAttribute(name));
            }
        }
        return super.invoke(invocation, targetObject);
    }
}


Изменяем конфигурацию сервиса:


<bean id="cookiesBasedRemoteInvocationExecutor" class="foo.CookiesBasedRemoteInvocationExecutor"/>

<bean name="/FooService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
   <property name="service" ref="fooSearcher"/>
   <property name="serviceInterface" value="foo.FooService"/>
   <property name="remoteInvocationExecutor" ref="cookiesBasedRemoteInvocationExecutor"/>
</bean>



Вуаля. Проверяем. Измененный код сервиса:


   public String hello(String name) {
      logger.info("Greeting "+ServerCookiesHolder.getContext().getCookie("realName"));
      return "Hello "+name;
   }






Код вызова сервиса на клиенте:

public void test() {
   CookiesHolder.setCookie("realName", "Foo");
   fooService.hello("Fred");
}


0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.