javaspring-remoting

how to set dynamic header in spring remoting


We need to call a Bean class using spring remoting and also set dynamic header in the call. We can set custom HttpInvokerRequestExecutor in the HttpInvokerProxyFactoryBean and add header but how to set dynamic header generated on the fly for the request?

In the Config class, declaring the HttpInvokerProxyFactoryBean
@Bean
@Qualifier("service")
public HttpInvokerProxyFactoryBean invoker() {
    HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
    invoker.setServiceUrl(url);
    invoker.setServiceInterface(Service.class);
    return invoker;
}

In the invoker class
@Autowired
Service service;

public void invoke(Bean bean) {
    service.process(bean);
}

Solution

  • Its been a long time that I used spring remoting but as far as I remember I found a solution to this by subclassing of SimpleHttpInvokerRequestExecutor which is default when you do not set any custom request executor to HttpInvokerProxyFactoryBean.

    IMHO you can write a custom request executor which you can set custom header values and a simple helper component which sets the dynamically provided values to the executor before the next request.

    CustomHttpInvokerRequestExecutor:

    public class CustomHttpInvokerRequestExecutor extends SimpleHttpInvokerRequestExecutor {
    
    private Map<String, String> headers;
    
    public void setHeaders(Map<String, String> headers) {
        this.headers = headers;
    }
    
    @Override
    protected void prepareConnection(HttpURLConnection connection, int contentLength) throws IOException {
        super.prepareConnection(connection, contentLength);
    
        if (headers != null) {
            // adding our custom headers
            for (String headerName : headers.keySet()) {
                connection.setRequestProperty(headerName, headers.get(headerName));
            }
            // do not want to persist headers for another request!
            headers.clear();
        }
    }
    }
    

    CustomRemoteExecutor:

    @Component
    public class CustomRemoteExecutor {
    
    @Autowired
    private HttpInvokerProxyFactoryBean factoryBean;
    
    /*
     * May be you should need a synchronized modifier here if there is possibility
     * of multiple threads access here at the same time
     */
    public void executeInTemplate(Map<String, String> headers, Runnable task) {
        CustomHttpInvokerRequestExecutor executor = (CustomHttpInvokerRequestExecutor) factoryBean.getHttpInvokerRequestExecutor();
        executor.setHeaders(headers);
        task.run();
    }
    

    }

    And then you can use it by below:

    @Bean
    @Qualifier("service")
    public HttpInvokerProxyFactoryBean invoker() {
        HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
        invoker.setServiceUrl(testUrl);
        invoker.setServiceInterface(Service.class);
        // set our custom request executor
        CustomHttpInvokerRequestExecutor executor = new CustomHttpInvokerRequestExecutor();
        invoker.setHttpInvokerRequestExecutor(executor);
    
        return invoker;
    }
    
    @Autowired
    CustomRemoteExecutor executor;
    
    @Autowired
    Service service;
    
    public void invoke(Bean bean) {
    
        // when you need custom headers
        Map<String, String> headers = new HashMap<>();
        headers.put("CUSTOM_HEADER", "CUSTOM_VALUE");
        headers.put("CUSTOM_HEADER2", "CUSTOM_VALUE2");
        executor.executeInTemplate(headers, () -> service.process(bean));
    
    }
    

    There is one drawback here as I also stated in comments, if you execute your proxy service client in a multithreaded environment (server to server requests may be) you should consider to make executeInTemplate method synchronized

    An addition to my answer if your service method needs to return some object then you can add another helper method to CustomRemoteExecutor and use it when you need to return something. The method can have the same name here so it can overload the former one which is much better I think.

    public <T> T executeInTemplate(Map<String, String> headers, Callable<T> task) {
        CustomHttpInvokerRequestExecutor executor = (CustomHttpInvokerRequestExecutor) factoryBean.getHttpInvokerRequestExecutor();
        executor.setHeaders(headers);
    
        try {
            return task.call();
        } catch (Exception e) {
            // it is better to log this exception by your preferred logger (log4j, logback
            // etc.)
            e.printStackTrace();
        }
    
        return null;
    }
    

    And again you can use like below:

    @Autowired
    CustomRemoteExecutor executor;
    
    @Autowired
    ISampleService service;
    
    public void invoke(Bean bean) {
    
        // when you need custom headers
        Map<String, String> headers = new HashMap<>();
        headers.put("CUSTOM_HEADER", "CUSTOM_VALUE");
        headers.put("CUSTOM_HEADER2", "CUSTOM_VALUE2");
    
        // assume that service.returnSomething() method returns String
        String value = executor.executeInTemplate(headers, () -> service.returnSomething(bean));
    
    }
    

    Hope it helps.