Search code examples
apache-camelwildflyundertow

Apache camel Rest service with undertow and nordic letters


I have a rest service that use undertow componenet. I use wildfly and wildfly patch 4.7.0 (apache camel 2.19)

I have problem to reply with nordic letters from my Rest service. Tested with postman.

The code is:

 @Override
    public void configure() throws Exception {
        restConfiguration().component("undertow");
        rest("/hello").post("/{name}").consumes("application/json").to("direct:testpost");
        from("direct:testpost")
                .routeId("testpost")
                .log("${body}")
                .transform().jsonpath("test");
    }

And when I send:

POST /hello/Anton HTTP/1.1 Host: 192.168.56.103:8080 Content-Type: application/json ; charset=UTF-8 Cache-Control: no-cache Postman-Token: c7ed9034-8b58-d104-9804-a1ff60a26f65

{"test": "test with Å"}

I get the error:

 2017-06-07 07:06:16,253 INFO  [testput] (default task-5) {"test":
 "test with Å"}

 2017-06-07 07:06:16,258 ERROR [io.undertow.request] (default task-5)
 UT005071: Undertow request failed HttpServerExchange{ POST
 /hello/Anton request {Accept=[*/*],
 Postman-Token=[a3f9c986-e6d1-1f41-e4f8-54f3e38678c3],
 Accept-Language=[sv-SE,sv;q=0.8,en-US;q=0.6,en;q=0.4],
 Cache-Control=[no-cache], Accept-Encoding=[gzip, deflate],
 Origin=[chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop],
 User-Agent=[Mozilla/5.0 (Windows NT 6.1; Win64; x64)
 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86
 Safari/537.36], Connection=[keep-alive], Content-Length=[24],
 Content-Type=[application/json ; charset=UTF-8],
 Host=[192.168.56.103:8080]} response {Accept=[*/*],
 Postman-Token=[a3f9c986-e6d1-1f41-e4f8-54f3e38678c3],
 Accept-Language=[sv-SE,sv;q=0.8,en-US;q=0.6,en;q=0.4], name=[Anton],
 X-Powered-By=[Undertow/1], Accept-Encoding=[gzip, deflate],
 breadcrumbId=[ID-localhost-39384-1496833334654-11-5],
 Server=[WildFly/10],
 Origin=[chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop],
 User-Agent=[Mozilla/5.0 (Windows NT 6.1; Win64; x64)
 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86
 Safari/537.36], Content-Type=[application/json ; charset=UTF-8]}}:
 org.apache.camel.TypeConversionException: Error during type conversion
 from type: java.lang.String to the required type: java.nio.ByteBuffer
 with value test with Å due java.nio.BufferOverflowException
         at org.apache.camel.impl.converter.BaseTypeConverterRegistry.createTypeConversionException(BaseTypeConverterRegistry.java:629)
         at org.apache.camel.impl.converter.BaseTypeConverterRegistry.convertTo(BaseTypeConverterRegistry.java:150)
         at org.apache.camel.impl.converter.BaseTypeConverterRegistry.convertTo(BaseTypeConverterRegistry.java:121)
         at org.apache.camel.component.undertow.UndertowConsumer.handleRequest(UndertowConsumer.java:135)
         at io.undertow.server.Connectors.executeRootHandler(Connectors.java:202)
         at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:805)
         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
         at java.lang.Thread.run(Thread.java:748) Caused by: org.apache.camel.RuntimeCamelException:
 java.nio.BufferOverflowException
         at org.apache.camel.util.ObjectHelper.wrapRuntimeCamelException(ObjectHelper.java:1756)
         at org.apache.camel.util.ObjectHelper.invokeMethod(ObjectHelper.java:1355)
         at org.apache.camel.impl.converter.StaticMethodTypeConverter.convertTo(StaticMethodTypeConverter.java:59)
         at org.apache.camel.impl.converter.BaseTypeConverterRegistry.doConvertTo(BaseTypeConverterRegistry.java:306)
         at org.apache.camel.impl.converter.BaseTypeConverterRegistry.convertTo(BaseTypeConverterRegistry.java:133)
         ... 7 more Caused by: java.nio.BufferOverflowException
         at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:189)
         at java.nio.ByteBuffer.put(ByteBuffer.java:859)
         at org.apache.camel.converter.NIOConverter.toByteBuffer(NIOConverter.java:102)
         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
         at java.lang.reflect.Method.invoke(Method.java:498)
         at org.apache.camel.util.ObjectHelper.invokeMethod(ObjectHelper.java:1351)
         ... 10 more

undertow configuration:

 <subsystem xmlns="urn:jboss:domain:undertow:3.1">
            <buffer-cache name="default"/>
            <server name="default-server">
                <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/>
                <https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/>
                <host name="default-host" alias="localhost">
                    <location name="/" handler="welcome-content"/>
                    <filter-ref name="server-header"/>
                    <filter-ref name="x-powered-by-header"/>
                </host>
            </server>
            <servlet-container name="default">
                <jsp-config/>
                <websockets/>
            </servlet-container>
            <handlers>
                <file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
            </handlers>
            <filters>
                <response-header name="server-header" header-name="Server" header-value="WildFly/10"/>
                <response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow/1"/>
            </filters>
        </subsystem>

So have anyone any ideas how I can fix this? All help is appreciated!


Solution

  • It's a bug in org.apache.camel.converter.NIOConverter (2.18.4)

    public static ByteBuffer toByteBuffer(String value, Exchange exchange) {
        ByteBuffer buf = ByteBuffer.allocate(value.length());
        byte[] bytes = null;
        if (exchange != null) {
            String charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
            if (charsetName != null) {
                try {
                    bytes = value.getBytes(charsetName);
                } catch (UnsupportedEncodingException e) {
                    LOG.warn("Cannot convert the byte to String with the charset " + charsetName, e);
                }
            }
        }
        if (bytes == null) {
            bytes = value.getBytes();
        }
        buf.put(bytes);
        buf.flip();
        return buf;
    }
    

    The ByteBuffer is allocated with String.length() ignoring the fact that it might contain double byte characters.

    I'm working around this bug by replacing the converter in the registry using an eventlistener.

    void onContextStarting(@Observes CamelContextStartingEvent event) {
        def context = event.getContext();
        def registry = context.getTypeConverterRegistry();
    
        registry.setTypeConverterExistsLoggingLevel(LoggingLevel.INFO)
        registry.setTypeConverterExists(TypeConverterExists.Override)
        registry.addTypeConverter(ByteBuffer.class,String.class, new StringToByteBufferConverter());
        registry.setTypeConverterExists(TypeConverterExists.Ignore)
    }
    

    The converter I replaced it with uses the byte.length to allocate the ByteBuffer so the buffer matches the actualy byte count for the string:

    import org.apache.camel.Exchange;
    import org.apache.camel.support.TypeConverterSupport;
    
    import java.io.UnsupportedEncodingException;
    import java.nio.ByteBuffer;
    
    public class StringToByteBufferConverter extends TypeConverterSupport {
    
      public <T> T convertTo(Class<T> type, Exchange exchange, Object object) {
        String value = (String)object;
        byte[] bytes = null;
        if (exchange != null) {
            String charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
            if (charsetName != null) {
                try {
                    bytes = value.getBytes(charsetName);
                } catch (UnsupportedEncodingException e) {
                    throw new UnsupportedOperationException(e);
                }
            }
        }
        if (bytes == null) {
            bytes = value.getBytes();
        }
        ByteBuffer buf = ByteBuffer.allocate(bytes.length);
        buf.put(bytes);
        buf.flip();
        return (T)buf;
      }
    
    }
    

    You can also work around this bug by converting the body to a type other than String to circumvent the bad converter:

    @Override
    public void configure() throws Exception {
        restConfiguration().component("undertow");
        rest("/hello").post("/{name}").consumes("application/json").to("direct:testpost");
        from("direct:testpost")
                .routeId("testpost")
                .log("${body}")
                .transform().jsonpath("test")
                .convertBodyTo(byte[].class);
    }