Groovy HTTP Builder: Empty POST response causes SAXParseException

2013-12-03 java groovy httpbuilder

I am implementing REST client for YouTrack API. I am getting an exception whenever my POST requests return empty response (there's just a valid header). The HTTP status code is 200. That's the stacktrace:

[Fatal Error] :-1:-1: Premature end of file.
gru 03, 2013 10:34:49 AM groovyx.net.http.HTTPBuilder doRequest
WARNING: Error parsing 'application/xml; charset=UTF-8' response
org.xml.sax.SAXParseException; Premature end of file.
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
at groovy.util.XmlSlurper.parse(XmlSlurper.java:181)
at groovy.util.XmlSlurper.parse(XmlSlurper.java:234)
at groovyx.net.http.ParserRegistry.parseXML(ParserRegistry.java:267)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1085)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:952)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:909)
at groovy.lang.Closure.call(Closure.java:423)
at groovy.lang.Closure.call(Closure.java:439)
at groovyx.net.http.HTTPBuilder.parseResponse(HTTPBuilder.java:561)
at groovyx.net.http.HTTPBuilder.doRequest(HTTPBuilder.java:494)
at groovyx.net.http.RESTClient.post(RESTClient.java:140)
at groovyx.net.http.RESTClient$post.callCurrent(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:49)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
at groovy.YouTrackRest.releaseVersion(YouTrackRest.groovy:66)
at groovy.YouTrackRest$releaseVersion$0.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at YouTrack.run(YouTrack.groovy:13)
[...]

I have debugged and found out what's the direct cause for that. I noticed that in groovyx.net.http.HTTPBuilder:492 before parsing it is checked whether response is empty but in my case the call entity.getContentLength() returns -1. After Apache documentation for HttpEntity the number of bytes of the content, or a negative number if unknown

My client implementation is very plain. I have worked the problem around by catching the HttpResponseException and asserting statusCode 200. I am still getting the stacktrace in my console, but that's probably just the logger setting.

My implementation:

import groovyx.net.http.RESTClient

class YouTrackRest extends RESTClient { 
[...]
def activateVersion(versionNumber) {
    try {
        post(
                path: getVersionPath(versionNumber),
                body: [
                        colorIndex: ACTIVE_COLOR_INDEX,
                        released: false,
                        releaseDate: null,
                        archived: false,
                        newName: versionNumber

                ]
        )
    } catch (HttpResponseException ex) {
        assert ex.response.status == 200
    }
}

EDIT: These are the response headers

Server: nginx
Date: Fri, 06 Dec 2013 10:36:10 GMT
Content-Type: application/xml; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Set-Cookie: JSESSIONID=ny2s3cxwsvveya6vgwj2fmig;Path=/youtrack
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache, no-store, no-transform, must-revalidate

Present Transfer-Encoding: chunked header explains missing 'Content-Length' header. That has to be the reason for unknown Content Length. Now, according to Wikipedia The data transfer is terminated by a final chunk of length zero. So maybe is this case the first chunk is also the last one?

Both, the Web Server and the Http client library are third party for me. I would like to raise an issue by whoever misbehaves:

  • either YouTrack REST WS promisses XML and returns nothing
  • or groovyx.net.http.HTTPBuilder fails to process chunked encoded responses.

I will be very helpful for some expertise to help me understand the problem.

Answers

Apparently the problem was caused by non-standard WS response: HTTP status 200 and empty response body. Either HTTP status 204 is expected, or valid XML body and status 200.

I have reported an issue at WS providers. Hopefully it will get resolved. I am marking the question as answered

Thanks @tim_yates and @Ian Roberts for your helpful comments.

I just ran into this same problem using . I was able to work around it with:

def service = new HTTPBuilder(...)
service.request(Method.POST, ContentType.JSON) {
    uri.path = ...
    body = ...

    response.'200' = { resp ->
            logInfo resp.statusLine
    }

    response.success = { resp, result ->
            logInfo resp.statusLine
    }

    response.failure = { resp ->
            logError resp.statusLine
    }
}

Essentially do not try to parse the body, if response is 200.

Related