Search
Write a publication
Pull to refresh

Аналог 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
 * <p>Attribute keys have to be unique, and no overriding of existing  
 * attributes is allowed.  
 * <p>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");
}

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.