5
votes

I'm trying to implement some sort of proxy as part of my data flow, I want to receive a http-request on my inbound gateway and pass it through outbound gateway. I want preserve all query string parameters. My gateways configuration is:

<int:channel id="searchRequestChannel" />
<int:channel id="searchReplyChannel" />

<int-http:inbound-gateway id="searchRequestInboundGateway"      
    supported-methods="GET" 
    request-channel="searchRequestChannel"
    reply-channel="searchReplyChannel"      
    path="/services/normalization"
    reply-timeout="50000"
/>

<int-http:outbound-gateway id="searchServiceGateway"
    http-method="GET"
    request-channel="searchRequestChannel"
    url="http://localhost:8080/query"
    extract-request-payload="false"
    expected-response-type="java.lang.String"
    reply-timeout="50000"
    charset="UTF-8"
/>

I expected that it would work as follows:

  • Client send request to the inbound gateway /services/normalization:

    GET /services/normalization q=cat&exclude=black

  • Inbound gateway receives request and send it through searchRequestChannel to the outbound gateway.

  • Outbound gateway sends whole request to the external service:

    GET /query q=cat&exclude=black

But on practice, outbound gateway sends empty request that does not contains any query arguments:

GET /query

So my question, what's easiest way to send the http-request that was accepted on inbound gateway through outbound gateway. In other words how can I implement simple proxy by spring integration tools?

2

2 Answers

4
votes

This is a bit of a kludge, but works; the DispatcherServlet binds the request to the thread...

<int-http:inbound-gateway id="searchRequestInboundGateway"      
    supported-methods="GET" 
    request-channel="searchRequestEnricherChannel"
    reply-channel="searchReplyChannel"      
    path="/services/normalization{queryString}"
    reply-timeout="50000"
/>

<int:header-enricher input-channel="searchRequestEnricherChannel" output-channel="searchRequestChannel">
    <int:header name="queryString" 
        expression="T(org.springframework.web.context.request.RequestContextHolder).requestAttributes.request.queryString" />
</int:header-enricher>

and then on the outbound side, use

<int-http:outbound-gateway id="searchServiceGateway"
    http-method="GET"
    request-channel="searchRequestChannel"
    url="http://localhost:8080/query?{queryString}"
    encode-uri="false"
    extract-request-payload="false"
    expected-response-type="java.lang.String"
    reply-timeout="50000"
    charset="UTF-8">
    <uri-variable name="queryString" expression="headers.queryString" />
</int-http:outbound-gateway>

However, this won't work with 2.2.x and earlier because the query string is encoded on the outbound side (foo=bar&baz=qux becomes foo%3Dbar%26baz%3Dqux). In 3.0 we have added the ability to not encode the URI using an attribute by using encode-uri="false". This is not yet available in a release, but it's available in 3.0.0.BUILD-SNAPSHOT.

EDIT:

The above is a general solution that will work for all query strings; if you know the actual parameters, another solution would be to extract each parameter separately and rebuild the query string on the outbound side...

<int-http:inbound-gateway ... >
    <int-http:header name="foo" expression="#requestParams.foo.get(0)"/>                          
    <int-http:header name="baz" expression="#requestParams.baz.get(0)"/>
</int-http:inbound-gateway>

<int-http:outbound-gateway request-channel="requestChannel" 
                           url="http://localhost:18080/http/receiveGateway?foo={foo}&amp;baz={baz}"
                           http-method="POST"
                           expected-response-type="java.lang.String">
    <int-http:uri-variable name="foo" expression="headers.foo"/>
    <int-http:uri-variable name="baz" expression="headers.baz"/>
</int-http:outbound-gateway>

On the inbound side, it would be better if we offered the queryString as a first class expression variable #queryString.

Please feel free to open an 'Improvement' JIRA Issue

4
votes

My own workaround solution is use a transformer that transforms parameters in the message payload (map of query string parameters) to prepared query string and use an url-expression in an outbound-gateway to avoid a query string encoding:

<bean id="payloadToQueryString" 
    class="com.dph.integration.PayloadToQueryStringTransformer" />

<int-http:inbound-gateway id="searchRequestInboundGateway"      
 supported-methods="GET"
 request-channel="searchRequestChannel"
 path="/services/normalization"
 reply-timeout="50000" />

<int:transformer input-channel="searchRequestChannel" 
     output-channel="searchGatewayChannel" 
     ref="payloadToQueryString" method="transform" />

<int-http:outbound-gateway id="searchServiceGateway"
    http-method="GET"
    request-channel="searchGatewayChannel"
    url-expression="'http://localhost:8080/query?' + payload"
    expected-response-type="java.lang.String"
    reply-timeout="50000"
    charset="UTF-8">
</int-http:outbound-gateway>

PayloadToQueryStringTransformer class is:

public class PayloadToQueryStringTransformer extends AbstractTransformer {

@Override
protected Object doTransform(final Message<?> message) throws Exception {
    return MessageBuilder
        .withPayload(urlEncodeUTF8(((MultiValueMap) message.getPayload()).toSingleValueMap()))
        .copyHeaders(message.getHeaders())
        .build();
}

private static String urlEncodeUTF8(final String s) {
    try {
        return URLEncoder.encode(s, "UTF-8");
    } catch (final UnsupportedEncodingException e) {
        throw new UnsupportedOperationException(e);
    }
}
private static String urlEncodeUTF8(final Map<?,?> map) {
    final StringBuilder sb = new StringBuilder();
    for (final Map.Entry<?,?> entry : map.entrySet()) {
        if (sb.length() > 0) {
            sb.append("&");
        }
        sb.append(String.format("%s=%s",
                urlEncodeUTF8(entry.getKey().toString()),
                urlEncodeUTF8(entry.getValue().toString())
                ));
    }
    return sb.toString();
}

}