Spock test - RESTClient: HttpResponseException: Not Found - spring

I want to write a test for a GET request when the API returns 404.
My test:
def "Should return 404 - object deleted before"() {
setup:
def advertisementEndpoint = new RESTClient( 'http://localhost:8080/' )
when:
def resp = advertisementEndpoint.get(
path: 'api/advertisement/1',
contentType: groovyx.net.http.ContentType.JSON
)
then:
resp.status == 404
}
My error:
14:24:59.294 [main] DEBUG o.a.h.impl.client.DefaultHttpClient -
Connection can be kept alive indefinitely 14:24:59.305 [main] DEBUG
groovyx.net.http.RESTClient - Response code: 404; found handler:
org.codehaus.groovy.runtime.MethodClosure#312aa7c 14:24:59.306 [main]
DEBUG groovyx.net.http.RESTClient - Parsing response as:
application/json 14:24:59.443 [main] DEBUG org.apache.http.wire - <<
"ba[\r][\n]" 14:24:59.444 [main] DEBUG org.apache.http.wire - <<
"{"timestamp":1436358299234,"status":404,"error":"Not
Found","exception":"com.pgssoft.exparo.web.ResourceNotFoundException","message":"No
message available","path":"/api/advertisement/1"}" 14:24:59.445 [main]
DEBUG org.apache.http.wire - << "[\r][\n]" 14:24:59.445 [main] DEBUG
org.apache.http.wire - << "0[\r][\n]" 14:24:59.446 [main] DEBUG
org.apache.http.wire - << "[\r][\n]" 14:24:59.446 [main] DEBUG
o.a.h.i.c.BasicClientConnectionManager - Releasing connection
org.apache.http.impl.conn.ManagedClientConnectionImpl#2ab4bc72
14:24:59.446 [main] DEBUG o.a.h.i.c.BasicClientConnectionManager -
Connection can be kept alive indefinitely 14:24:59.449 [main] DEBUG
groovyx.net.http.RESTClient - Parsed data to instance of: class
groovy.json.internal.LazyMap
groovyx.net.http.HttpResponseException: Not Found at
groovyx.net.http.RESTClient.defaultFailureHandler(RESTClient.java:263)
at groovy.lang.Closure.call(Closure.java:423) at
groovyx.net.http.HTTPBuilder$1.handleResponse(HTTPBuilder.java:503)
at
org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:218)
at
org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:160)
at groovyx.net.http.HTTPBuilder.doRequest(HTTPBuilder.java:515) at
groovyx.net.http.RESTClient.get(RESTClient.java:119) at
AdvertisementTest.Should return 404 - object delete
before(AdvertisementTest.groovy:79)

You need a failure handler for the underlying HTTPBuilder. From the HTTPBuilder javadoc:
You can also set a default response handler called for any status code
399 that is not matched to a specific handler. Setting the value outside a request closure means it will apply to all future requests
with this HTTPBuilder instance:
http.handler.failure = { resp ->
println "Unexpected failure: ${resp.statusLine}" }
Therefore:
#Grapes(
#Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.7.1')
)
import groovyx.net.*
import groovyx.net.http.*
def restClient = new RESTClient('http://localhost/wrong')
restClient.handler.failure = { resp -> resp.status }
def response = restClient.get([:])
assert response == 404

Related

How can you keep the websocket alive with Karate [duplicate]

This question already has an answer here:
How can I integrate socket.io on Karate
(1 answer)
Closed 1 year ago.
This is the way I tried to open a websocket connection and after running the test the connection is immediately closed. The problem is that in the async context even in test case i would like to be able to wait for the async responses from backend.
My implementation:
Feature: websocket testing
Scenario: only listening to websocket messages
* def demoBaseUrl = 'wss://bla bla bla/subscription'
* def handler = function(msg){ karate.signal(msg) }
* def socket = karate.webSocket(demoBaseUrl, handler)
* def txt = '{"type": "connection_init", "payload": {}}'
* socket.send(txt)
* def result = karate.listen(10000)
* print 'response:', result
The log after running the test:
Testing started at 09:44 ...
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/bin/java -Dorg.jetbrains.run.directory=/Users/wzhmiur/Desktop/karate/src/test/java/examples/users "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=55472:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/tools.jar:/Users/wzhmiur/Desktop/karate/build/classes/java/test:/Users/wzhmiur/Desktop/karate/build/resources/test:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.intuit.karate/karate-junit5/0.9.4/6e4231fc7c7cc40d151d9c2b1c16141f4943ffca/karate-junit5-0.9.4.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.intuit.karate/karate-apache/0.9.4/3a4d731189822c0fdafe4abed6dc44c3dd28acdd/karate-apache-0.9.4.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/net.masterthought/cucumber-reporting/3.8.0/3412fc79e6e48ca0c961180a302020e36aaea71a/cucumber-reporting-3.8.0.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.intuit.karate/karate-core/0.9.4/7f698c207be627e3818ff932984afa9d366efbc7/karate-core-0.9.4.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.junit.jupiter/junit-jupiter-api/5.4.0/1a09f25a160f71c267f9ebe3b229b17805c683e9/junit-jupiter-api-5.4.0.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpmime/4.5.5/8281b24b8a493374cd2aa8a90c4156588f7dbcb6/httpmime-4.5.5.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpclient/4.5.5/1603dfd56ebcd583ccdf337b6c3984ac55d89e58/httpclient-4.5.5.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.slf4j/jcl-over-slf4j/1.7.25/f8c32b13ff142a513eeb5b6330b1588dcb2c0461/jcl-over-slf4j-1.7.25.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/7c4f3c474fb2c041d8028740440937705ebb473a/logback-classic-1.2.3.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.6/a7cb258b9c36f49c148834a3a35b53fe73c28777/log4j-core-2.6.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.6/1300b198716dccca49d1740cf6a737d083be0d06/log4j-api-2.6.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-databind/2.8.5/b3035f37e674c04dafe36a660c3815cc59f764e2/jackson-databind-2.8.5.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.apache.velocity/velocity/1.7/2ceb567b8f3f21118ecdec129fe1271dbc09aa7a/velocity-1.7.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/velocity-tools/velocity-tools/1.4/4e1f4d507030a00959f4c0c7fcc60b3565617d08/velocity-tools-1.4.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/joda-time/joda-time/2.9.6/e370a92153bf66da17549ecc78c69ec6c6ec9f41/joda-time-2.9.6.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.5/6c6c702c89bfff3cd9e80b04d668c5e190d588c6/commons-lang3-3.5.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/2.5/2852e6e05fbb95076fc091f6d1780f1f8fe35e0f/commons-io-2.5.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/net.lingala.zip4j/zip4j/1.3.2/4ba84e98ee017b74cb52f45962f929a221f3074c/zip4j-1.3.2.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.codehaus.plexus/plexus-utils/3.0.24/b4ac9780b37cb1b736eae9fbcef27609b7c911ef/plexus-utils-3.0.24.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.jsoup/jsoup/1.10.1/645f1ad2f6f4cbad1cde4c483eae71e4051be6ef/jsoup-1.10.1.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.googlecode.owasp-java-html-sanitizer/owasp-java-html-sanitizer/20160924.1/f025ce55755bcdb1cf049e4c271d193c5610e182/owasp-java-html-sanitizer-20160924.1.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/20.0/89507701249388e1ed5ddcf8c41f4ce1be7831ef/guava-20.0.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.jayway.jsonpath/json-path/2.1.0/397bd8076ff9581ad2ebad787da45ba76ee39112/json-path-2.1.0.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/info.cukes/cucumber-java/1.2.5/2197dfa9cd7899ddce136a356994ac21f438f80/cucumber-java-1.2.5.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.yaml/snakeyaml/1.24/13a9c0d6776483c3876e3ff9384f9bb55b17001b/snakeyaml-1.24.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/de.siegmar/fastcsv/1.0.3/356512f93d0ef5e682ccfac3608864686b45c11b/fastcsv-1.0.3.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/info.picocli/picocli/3.0.1/ed22a0e26d214540b4e189c58696f8b1ca409a1f/picocli-3.0.1.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-commons/1.4.0/e0b2ed8fac32ad6469d75d034e759f1969db8dda/junit-platform-commons-1.4.0.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.apiguardian/apiguardian-api/1.0.0/3ef5276905e36f4d8055fe3cb0bdcc7503ffc85d/apiguardian-api-1.0.0.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.opentest4j/opentest4j/1.1.1/efd9f971e91074491ea55b19f67b13470cf4fcdd/opentest4j-1.1.1.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.apache.httpcomponents/httpcore/4.4.9/a86ce739e5a7175b4b234c290a00a5fdb80957a0/httpcore-4.4.9.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/commons-codec/commons-codec/1.10/4b95f4897fa13f2cd904aee711aeafc0c5295cd8/commons-codec-1.10.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.3/864344400c3d4d92dfeb0a305dc87d953677c03c/logback-core-1.2.3.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-annotations/2.8.0/45b426f7796b741035581a176744d91090e2e6fb/jackson-annotations-2.8.0.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/com.fasterxml.jackson.core/jackson-core/2.8.5/60d059f5d2930ccd1ef03535b713fd9f933d1ba7/jackson-core-2.8.5.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/commons-collections/commons-collections/3.2.1/761ea405b9b37ced573d2df0d1e3a4e0f9edc668/commons-collections-3.2.1.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/commons-lang/commons-lang/2.4/16313e02a793435009f1e458fa4af5d879f6fb11/commons-lang-2.4.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/net.minidev/json-smart/2.2/53200a9d80fff3106dc435de1350cdd20b757819/json-smart-2.2.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/info.cukes/cucumber-core/1.2.5/7255a9d8e0c3b0f9e3cd80503c91c2b088b3d9b5/cucumber-core-1.2.5.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/commons-logging/commons-logging/1.2/4bfc12adfe4842bf07b657f0369c4cb522955686/commons-logging-1.2.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/net.minidev/accessors-smart/1.1/a527213f2fea112a04c9bdf0ec0264e34104cd08/accessors-smart-1.1.jar:/Users/wzhmiur/.gradle/caches/modules-2/files-2.1/org.ow2.asm/asm/5.0.3/dcc2193db20e19e1feca8b1240dbbc4e190824fa/asm-5.0.3.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar:/Applications/IntelliJ IDEA.app/Contents/plugins/cucumber-java/lib/cucumber-jvmFormatter.jar" cucumber.api.cli.Main --plugin org.jetbrains.plugins.cucumber.java.run.CucumberJvmSMFormatter --monochrome --glue com.intuit.karate /Users/wzhmiur/Desktop/karate/src/test/java/examples/users/apollo_subs.feature
command: cucumber.api.cli.Main --plugin org.jetbrains.plugins.cucumber.java.run.CucumberJvmSMFormatter --monochrome --glue com.intuit.karate /Users/wzhmiur/Desktop/karate/src/test/java/examples/users/apollo_subs.feature
09:44:15.522 [main] INFO com.intuit.karate - karate.env system property was: null
09:44:16.549 [nioEventLoopGroup-2-1] DEBUG c.i.k.netty.WebSocketClientHandler - websocket client connected
09:44:16.591 [nioEventLoopGroup-2-1] ERROR c.i.k.netty.WebSocketClientHandler - websocket exception: jdk.nashorn.internal.runtime.Undefined cannot be cast to java.lang.Boolean
09:44:16.595 [main] INFO com.intuit.karate - [print] response: {"type":"connection_ack","id":"c3c41faf-bfa9-4e35-a639-46b4382bf452"}
09:44:16.595 [nioEventLoopGroup-2-1] DEBUG c.i.k.netty.WebSocketClientHandler - websocket client disconnected
HTML report: (paste into browser to view) | Karate version: 0.9.4
file:/Users/wzhmiur/Desktop/karate/target/surefire-reports/src.test.java.examples.users.apollo_subs.html
---------------------------------------------------------
Process finished with exit code 0
Please let me know if this possible and it should be done properly.
Progress:
Then I don't understand how can you send messages, open websocket connection and give me accounts...and this could come later from microservices? How can you send messages?
Scenario: only listening to websocket messages
* def demoBaseUrl = 'websocket/subscription'
* def handler = function(msg){ karate.signal(msg) }
* def socket = karate.webSocket(demoBaseUrl, handler)
* def txt = '{"type": "connection_init", "payload": {}}'
* socket.send(txt)
* def result = karate.listen(5000)
* def subscribe = 'subscription { showTime(howManyTimes: 4){ now }'
* socket.send(subscribe)
* def result = karate.listen(5000)
* print 'response:', result
* def message = '{"type": "start", "id":"3", "payload": {"query": "query myQuery { pings { id } }", "operationName": "myQuery"}}'
* socket.send(message)
* def result = karate.listen(5000)
* print 'response:', result
log:
Testing started at 11:26 ...
--glue com.intuit.karate /Users/wzhmiur/Desktop/karate/src/test/java/examples/users/apollo_subs.feature
command: cucumber.api.cli.Main --plugin org.jetbrains.plugins.cucumber.java.run.CucumberJvmSMFormatter --monochrome --glue com.intuit.karate /Users/wzhmiur/Desktop/karate/src/test/java/examples/users/apollo_subs.feature
11:26:24.779 [main] INFO com.intuit.karate - karate.env system property was: null
11:26:26.020 [nioEventLoopGroup-2-1] DEBUG c.i.k.netty.WebSocketClientHandler - websocket client connected
11:26:26.211 [nioEventLoopGroup-2-1] ERROR c.i.k.netty.WebSocketClientHandler - websocket exception: jdk.nashorn.internal.runtime.Undefined cannot be cast to java.lang.Boolean
11:26:26.218 [nioEventLoopGroup-2-1] DEBUG c.i.k.netty.WebSocketClientHandler - websocket client disconnected
11:26:31.226 [main] INFO com.intuit.karate - [print] response:
11:26:36.233 [main] INFO com.intuit.karate - [print] response:
HTML report: (paste into browser to view) | Karate version: 0.9.4
file:/Users/wzhmiur/Desktop/karate/target/surefire-reports/src.test.java.examples.users.apollo_subs.html
---------------------------------------------------------
Process finished with exit code 0
Progress:
Feature: subscription
Scenario: only listening to websocket messages
* def handler = function(msg){ karate.signal(msg)}
* def demoBaseUrl = 'URL/subscription'
* string txt = '{"type": "connection_init"}}'
* string message = '{"type": "start", "id":"3", "payload": {"query": "query myQuery { pings { id } }", "operationName": "myQuery"}}'
* def options = {"type": "connection_init", "payload":{ "keepAlive":"true" }}
* def socket = karate.webSocket(demoBaseUrl, handler)
* socket.send(txt)
* json result = karate.listen(5000)
* print 'response:', result
* def id = result.id
* match result == {type: 'connection_ack', id: '#(id)'}
* socket.send(message)
* def result1 = karate.listen(5000)
* print 'response:', result1
This example is working fine for me, so I don't know what is different in your case:
* def socket = karate.webSocket('ws://echo.websocket.org')
* socket.send('hello world!')
* def result = socket.listen(5000)
* match result == 'hello world!'
* socket.send('another message')
* def result = socket.listen(5000)
* match result == 'another message'
So if you are still stuck, please follow this process: https://github.com/intuit/karate/wiki/How-to-Submit-an-Issue

How do I get a byte[] (image) from a webservice using micronaunt HttpClient

I am porting a Grails 3.1 library for using some internal webservices to Grails 4.0. One of the services provides an image of a requested employee upon request. I am having difficulty implementing the (micronaut) HttpClient code to process the request - specifically to get a proper byte[] that is the returned image.
A simple curl command on the command line works with the service:
curl -D headers.txt -H 'Authorization:Basic <encodedKeyHere>' https:<serviceUrl> >> image.jpg
and the image is correct. The header.txt is:
HTTP/1.1 200
content-type: image/jpeg;charset=UTF-8
date: Tue, 27 Aug 2019 20:05:43 GMT
x-ratelimit-limit: 100
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
x-ratelimit-remaining: 99
X-RateLimit-Reset: 38089
x-ratelimit-reset: 15719
Content-Length: 11918
Connection: keep-alive
The old library uses the groovyx.net.http.HTTPBuilder and simply does:
http.request(Method.GET, ContentType.BINARY) {
uri.path = photoUrlPath
uri.query = queryString
headers.'Authorization' = "Basic $encoded".toString()
response.success = { resp, inputstream ->
log.info "response status: ${resp.statusLine}"
return ['status':resp.status, 'body':inputstream.getBytes()]
}
response.failure = { resp ->
return ['status':resp.status,
'error':resp.statusLine.reasonPhrase,
body:resp.getEntity().getContent().getText()]
}
}
so returning the bytes from an inputStream. This works.
I've tried several things using the micronaut HttpClient, both with the low level API and with the declarative API.
A simple example with the declarative API:
#Get(value='${photo.ws.pathurl}', produces = MediaType.IMAGE_JPEG)
HttpResponse<byte[]> getPhoto(#Header ('Authorization') String authValue,
#QueryValue("emplId") String emplId)
And than in the Service:
HttpResponse<byte[]> resp = photoClient.getPhoto(getBasicAuth(),emplId)
def status = resp.status() // code == 200 --> worked
def bodyStrOne = resp.getBody() // nope: get Optional.empty
// Tried different getBody(class) -> Can't figure out where the byte[]s are
// For example can do:
def buf = resp.getBody(io.netty.buffer.ByteBuf).value // Why need .value?
def bytes = buf.readableBytes() // Returns 11918 --> the expected value
byte[] ans = new byte[buf.readableBytes()]
buf.readBytes(ans) // Throws exception: io.netty.util.IllegalReferenceCountException: refCnt: 0
This "works" but the returned String looses some encoding that I can't reverse:
// Client - use HttpResponse<String>
#Get(value='${photo.ws.pathurl}', produces = MediaType.IMAGE_JPEG)
HttpResponse<String> getPhoto(#Header ('Authorization') String authValue,
#QueryValue("emplId") String emplId)
// Service
HttpResponse<String> respOne = photoClient.getPhoto(getBasicAuth(),emplId)
def status = respOne.status() // code == 200 --> worked
def bodyStrOne = respOne.getBody(String.class) // <-- RETURNS DATA..just NOT an Image..or encoded or something
String str = bodyStrOne.value // get the String data
// But these bytes aren't correct
byte[] ans = str.getBytes() // NOT an image..close but not.
// str.getBytes(StandardCharsets.UTF_8) or any other charset doesn't work
Everything I've tried with the ByteBuf classes throws the io.netty.util.IllegalReferenceCountException: refCnt: 0 exception.
Any direction/help would be greatly appreciated.
Running:
Grails 4.0
JDK 1.8.0_221
Groovy 2.4.7
Windows 10
IntellJ 2019.2
It must be Grails bug.
Add this line into logback.groovy:
logger("io.micronaut.http", TRACE)
Then you should see that the body was not empty but finally it ends with error Unable to convert response body to target type class [B. See the trace:
2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Status Code: 200 OK
2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Content-Type: image/jpeg
2019-09-11 11:19:16.235 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Content-Length: 11112
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Accept-Ranges: bytes
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Response Body
2019-09-11 11:19:16.237 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : ----
2019-09-11 11:19:16.238 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : ���� C
...
2019-09-11 11:19:16.241 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : ----
2019-09-11 11:19:16.243 TRACE --- [ntLoopGroup-1-4] i.m.http.client.DefaultHttpClient : Unable to convert response body to target type class [B
But when you try the same in standalone Microunaut application (add <logger name="io.micronaut.http" level="trace"/> into logback.xml) the result is different:
09:02:48.583 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Status Code: 200 OK
09:02:48.583 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Content-Type: image/jpeg
09:02:48.589 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - content-length: 23195
09:02:48.590 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - Response Body
09:02:48.590 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ----
09:02:48.612 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ���� C���
...
09:02:48.620 [nioEventLoopGroup-1-5] TRACE i.m.http.client.DefaultHttpClient - ----
Micronaut trace has no error.
Here is an example of declarative HTTP client which downloads random image from https://picsum.photos web site:
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
#Client('https://picsum.photos')
interface LoremPicsumClient {
#Get(value = '{width}/{height}', produces = MediaType.IMAGE_JPEG)
HttpResponse<byte[]> getImage(Integer width, Integer height)
}
And Spock unit test for it:
import io.micronaut.http.HttpStatus
import io.micronaut.test.annotation.MicronautTest
import spock.lang.Specification
import javax.inject.Inject
import java.nio.file.Files
import java.nio.file.Paths
#MicronautTest
class LoremPicsumClientSpec extends Specification {
#Inject
LoremPicsumClient client
void 'image is downloaded'() {
given:
def output = Paths.get('test')
when:
def response = client.getImage(300, 200)
then:
response.status == HttpStatus.OK
response.getBody().isPresent()
when:
Files.write(output, response.getBody().get())
then:
Files.probeContentType(output) == 'image/jpeg'
}
}
In Micronaut the test passes and an image is saved into the test file. But in Grails the test fails because HttpClient is not able to convert the response bytes into byte array or better into anything else then String.
We are currently using this implementation:
#Client(value = "\${image-endpoint}")
interface ImageClient {
#Get("/img")
fun getImageForAddress(
#QueryValue("a") a: String
): CompletableFuture<ByteArray>
}
works fine for us.
When I use the HttpResponse I get an error as well, couldn't make it work with that.
Documentation propose to send bytes via input stream but I didn't manage to make it work. The most brittle thing that HttpClient should Consume bytes but Server should Produce.
#Get(value = "/write", produces = MediaType.TEXT_PLAIN)
HttpResponse<byte[]> write() {
byte[] bytes = "test".getBytes(StandardCharsets.UTF_8);
return HttpResonse.ok(bytes); //
}

PUT request returns HTTP 400 Bas Request in Spring Mvc - only occasionally

I have a strange issue using Spring mvc: while I'm trying to update one of my resources via PUT, it returns 400 Bad Request for the very same request - but only occasionally.
In Firebug, I use right click "resend" function, so assume all my PUT requests are completely the same.
I also set the log level to ALL for org.springframework.web to find out what's happening on the server side. In case of the response is 200 OK, everything seems fine:
2015-04-10 10:40:20,794 [http-8443-exec-12] TRACE servlet.DispatcherServlet:1048 - Bound request context to thread: SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ org.apache.catalina.connector.RequestFacade#321fa1b9]]
2015-04-10 10:40:20,795 [http-8443-exec-12] DEBUG servlet.DispatcherServlet:845 - DispatcherServlet with name 'springRestAPI' processing PUT request for [/rest/sites/25]
2015-04-10 10:40:20,796 [http-8443-exec-12] TRACE servlet.DispatcherServlet:1101 - Testing handler map [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#6c9ddcc4] in DispatcherServlet with name 'springRestAPI'
2015-04-10 10:40:20,796 [http-8443-exec-12] DEBUG annotation.RequestMappingHandlerMapping:297 - Looking up handler method for path /sites/25
2015-04-10 10:40:20,797 [http-8443-exec-12] TRACE annotation.RequestMappingHandlerMapping:335 - Found 1 matching mapping(s) for [/sites/25] : [{[/sites/{siteId}],methods=[PUT],params=[],headers=[],consumes=[],produces=[],custom=[]}]
2015-04-10 10:40:20,797 [http-8443-exec-12] DEBUG annotation.RequestMappingHandlerMapping:302 - Returning handler method [public void something.SiteController.updateSite(long,something.resources.UpdatedSiteResource,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]
2015-04-10 10:40:20,797 [http-8443-exec-12] TRACE servlet.DispatcherServlet:1141 - Testing handler adapter [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#5ec2c20d]
2015-04-10 10:40:20,798 [http-8443-exec-12] DEBUG mvc.WebContentInterceptor:146 - Looking up cache seconds for [/sites/25]
2015-04-10 10:40:20,798 [http-8443-exec-12] DEBUG mvc.WebContentInterceptor:158 - Applying default cache seconds to [/sites/25]
2015-04-10 10:40:20,799 [http-8443-exec-12] DEBUG annotation.RequestResponseBodyMethodProcessor:135 - Reading [class something.resources.UpdatedSiteResource] as "application/json;charset=utf-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#4de52e6]
2015-04-10 10:40:21,280 [http-8443-exec-12] DEBUG servlet.DispatcherServlet:1018 - Null ModelAndView returned to DispatcherServlet with name 'springRestAPI': assuming HandlerAdapter completed request handling
2015-04-10 10:40:21,281 [http-8443-exec-12] TRACE servlet.DispatcherServlet:1058 - Cleared thread-bound request context: SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ org.apache.catalina.connector.RequestFacade#321fa1b9]]
2015-04-10 10:40:21,281 [http-8443-exec-12] DEBUG servlet.DispatcherServlet:996 - Successfully completed request
But when the response is 400, there is nothing in the log: it does not even reaches the DispatcherServlet!
Any idea, how to proceed with this?

Grails validate runtime error

I use grails validate() function in my very simple update action inside a controller. The problem is very starnge. validate() is not executed at all and giving no error and stopping the execution. I am using Grails 2.3.3
Only the log gives the following error:
Runtime error executing action
Here is my controller code:
def update() {
println(params);
def study = Study.findByUid(params.uid);
study.description = params.description;
println(study); //study is found and printed
study.validate();
println("here"); //not executed and code below is also not executed
if(study.hasErrors()){
study.errors.each{
println it
}
render 'not saved!'
}
if (!study.save()) {
println("Error");
withFormat renderInternalError
}
else {
render "OK"
}
}
Stacktrace:
Request received for '/study/update':
org.apache.catalina.connector.RequestFacade#1eaba95c
servletPath:/study/update
pathInfo:null
Security filter chain: [
SecurityContextPersistenceFilter
MutableLogoutFilter
RequestHolderAuthenticationFilter
SecurityContextHolderAwareRequestFilter
GrailsRememberMeAuthenticationFilter
GrailsAnonymousAuthenticationFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
2013-11-27 11:56:04,738 [http-bio-8080-exec-9] DEBUG filter.GrailsRememberMeAuthenticationFilter - SecurityContextHolder not populated with remember-me token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken#f6816ea3: Principal: grails.plugin.springsecurity.userdetails.GrailsUser#b29dace9: Username: creator#example.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#1c07a: RemoteIpAddress: 127.0.0.1; SessionId: A1813ED8541AAC773CEF349475DB24A2; Granted Authorities: ROLE_USER'
2013-11-27 11:56:04,738 [http-bio-8080-exec-9] DEBUG filter.GrailsAnonymousAuthenticationFilter - SecurityContextHolder not populated with anonymous token, as it already contained: '{0}'
2013-11-27 11:56:04,740 [http-bio-8080-exec-9] TRACE intercept.AnnotationFilterInvocationDefinition - new candidate for '{0}': '{1}':{2}
2013-11-27 11:56:04,740 [http-bio-8080-exec-9] TRACE intercept.AnnotationFilterInvocationDefinition - config for '{0}' is '{1}':{2}
2013-11-27 11:56:04,741 [http-bio-8080-exec-9] TRACE core.StandardWrapper - Returning non-STM instance
2013-11-27 11:56:04,743 [http-bio-8080-exec-9] DEBUG simple.MemoryPageFragmentCachingFilter - No cacheable annotation found for POST:/hdspro/grails/study/update.dispatch [controller=study, action=update]
2013-11-27 11:56:05,651 [http-bio-8080-exec-9] DEBUG errors.GrailsExceptionResolver - Resolving exception from handler [org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController#98f1784]: org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException: Executing action [update] of controller [com.digithurst.hdspro.StudyController] caused exception: Runtime error executing action
2013-11-27 11:56:05,651 [http-bio-8080-exec-9] DEBUG errors.GrailsExceptionResolver - Resolving to view '/error' for exception of type [org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException], based on exception mapping [java.lang.Exception]
2013-11-27 11:56:05,651 [http-bio-8080-exec-9] DEBUG errors.GrailsExceptionResolver - Exposing Exception as model attribute 'exception'
2013-11-27 11:56:05,951 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver - StackOverflowError occurred when processing request: [POST] /hdspro/study/update - parameters:
acessionNumber: 3
uid: 3
description: Elbogen123
Stacktrace follows:
org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException: Executing action [update] of controller [com.digithurst.hdspro.StudyController] caused exception: Runtime error executing action
at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:200)
at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63)
at grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter.doFilter(GrailsAnonymousAuthenticationFilter.java:53)
at grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter.doFilter(RequestHolderAuthenticationFilter.java:49)
at grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.java:82)
at grails.plugin.springsecurity.web.filter.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:102)
at grails.plugin.springsecurity.web.filter.DebugFilter.doFilter(DebugFilter.java:69)
......
/
Study class:
class Study extends Document {
String uid
String description
String accessionNumber
Date date
String toString(){
return "Study" + uid + ": " + description;
}
}
From the comments I've read, you say you did not add any constraints to the domain class. From what I know validate() uses these constraints to check if your instance has errors. Ex:
class CartType {
String name
static constraints = {
name blank: false, nullable:false, maxSize: 50
}
String toString(){name}
}
In this example class we have a cartType. When we call validate() grails will check the constraint block. In this case it will check that the name is not left blank in the form, it cannot be null and that it's max size is 50 characters.
Note: The constraints block can also help grails build the database.
If you did not declare any contraints validate() will not have anything to check against. You will also most likely get an error when a value is null, because, by default, nullable is set to false.

Play framework - java.nio.channels.ClosedChannelException

Here is the scenario.
I am using Play framework. Inside a given handler, the play framework calls my API webservice and returns the API response to the client. The client is calling the handler through an Ajax call. Sometimes the response comes fine but often i am seeing error response on the client side. Checking the logs of play framework, i see a java.nio.channels.ClosedChannelException.
I am using Play 2.1.1.
My API Webservice is running on localhost:8888. Play framework is running on 9000.
The API service response is correct. Play frameworks also executes the Callback correctly as i can see the logs. The error happens after the ok() call has been made from Play.
Here are the error logs for a failed request -
[debug] application - find...
[debug] application - id = 647110558
[trace] c.jolbox.bonecp - Check out connection [9 leased]
[trace] c.jolbox.bonecp - Check in connection [9 leased]
[debug] application - socialUser = SocialUser(UserId(647110558,facebook),Arvind,Batra,Arvind Batra,Some(arvindbatra#gmail.com),null,AuthenticationMethod(oauth2),null,Some(OAuth2Info(CAAHNVOUuNZAEBAMa3CPLUEsZA2Tp5xWGXylO9HggBY0TCfwsIn4iGUdlRMpuNPLxYcObKO5ZBZCU0ghS9ymHZC3s9YXpsfPix9AM1EhNyETvDR85HHYg8j7JO0h2WzGZBsKJdbFPhPmkD6ZBZAq6KTT8RLSQrmpfnHQZD,null,null,null)),null)
[info] application - Calling interest for fff
[info] application - user is not null
[trace] c.jolbox.bonecp - Check out connection [9 leased]
[info] application - interest=fff, userInfo:models.EBUser#14a420e1
[info] application - http://localhost:8888/api/add_interest/1/fff
[debug] c.n.h.c.p.n.NettyAsyncHttpProvider - Using cached Channel [id: 0x9d1dee2d, /127.0.0.1:50316 => localhost/127.0.0.1:8888]
for uri http://localhost:8888/api/add_interest/1/fff
[debug] c.n.h.c.p.n.NettyAsyncHttpProvider -
Using cached Channel [id: 0x9d1dee2d, /127.0.0.1:50316 => localhost/127.0.0.1:8888]
for request
DefaultHttpRequest(chunked: false)
GET /api/add_interest/1/fff HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Accept: */* User-Agent: NING/1.0
[debug] c.n.h.c.p.n.NettyAsyncHttpProvider -
Request DefaultHttpRequest(chunked: false)
GET /api/add_interest/1/fff HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Accept: */*
User-Agent: NING/1.0
Response DefaultHttpResponse(chunked: true)
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Thu, 04 Jul 2013 12:14:40 GMT
Transfer-Encoding: chunked
[debug] c.n.h.c.p.n.NettyConnectionsPool - Adding uri: http://localhost:8888 for channel [id: 0x9d1dee2d, /127.0.0.1:50316 => localhost/127.0.0.1:8888]
[info] application - {"status":"success"}
[info] application - {"status":"ok","exists":false}
[trace] play - Sending simple result: SimpleResult(200, Map(Content-Type -> application/json; charset=utf-8, Set-Cookie -> ))
[debug] play - java.nio.channels.ClosedChannelException
[trace] application - Exception caught in Netty
java.nio.channels.ClosedChannelException: null
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.cleanUpWriteBuffer(AbstractNioWorker.java:409) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.writeFromUserCode(AbstractNioWorker.java:127) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink.handleAcceptedSocket(NioServerSocketPipelineSink.java:99) ~[netty.jar:na]
at org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink.eventSunk(NioServerSocketPipelineSink.java:36) ~[netty.jar:na]
at org.jboss.netty.channel.Channels.write(Channels.java:725) ~[netty.jar:na]
at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.doEncode(OneToOneEncoder.java:71) ~[netty.jar:na]
[debug] c.n.h.c.p.n.NettyConnectionsPool - Entry count for : http://localhost:8888 : 2
Here is my sample code -
public static Result addInterestCallback(WS.Response response) {
if (response == null) {
return badRequest();
}
ObjectNode result = (ObjectNode) response.asJson();
try {
Logger.info(result.toString());
if (result.has("status")) {
String status = result.get("status").getTextValue();
if(status.equals("error")) {
result.put("error", "Oops, cannot process your request. Sorry.");
Logger.info("error");
return badRequest(result);
}
else if(status.equals("exists")) {
result.put("exists",true);
}
else {
result.put("exists",false);
}
result.put("status", "ok");
} else {
//do something
Logger.info("result has no status");
}
Logger.info(result.toString());
} catch (Exception e) {
e.printStackTrace();
}
return ok(result);
}
#BodyParser.Of(BodyParser.Json.class)
#SecureSocial.UserAwareAction
public static Result addInterest() {
JsonNode json = request().body().asJson();
String interestName = json.findPath("interestName").getTextValue();
Logger.info("Calling interest for " + interestName);
Identity user = (Identity) ctx().args.get(SecureSocial.USER_KEY);
if (user == null) {
ObjectNode result = Json.newObject();
result.put("error", "requires-login");
Http.Context ctx = Http.Context.current();
ctx.flash().put("error", play.i18n.Messages.get("securesocial.loginRequired"));
result.put("redirect", RoutesHelper.login().absoluteURL(ctx.request(), IdentityProvider.sslEnabled()));
return ok(result);
}
Logger.info("user is not null");
if(interestName == null) {
ObjectNode result = Json.newObject();
result.put("error", "Empty input");
return badRequest(result);
}
//get user
EBUser ebUser = Application.getEBUser();
Logger.info("interest="+interestName+", userInfo:" + ebUser.toString());
//Call addInterst API.
String apiEndpoint = Play.application().configuration().getString(AppConstants.EB_API_ENDPOINT);
String url = "";
try {
url = apiEndpoint + "add_interest/" + ebUser.getId() + "/" + URLEncoder.encode(interestName, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
ObjectNode result = Json.newObject();
result.put("error", "Cant parse interest properly");
Logger.info("error " + result.toString());
return badRequest(result);
}
Logger.info(url);
Promise<WS.Response> promiseOfAPI = WS.url(url).get();
Promise<Result> promiseOfResult = promiseOfAPI.map(
new Function<WS.Response, Result>() {
#Override
public Result apply(WS.Response response) throws Throwable {
return addInterestCallback(response);
}
});
return async(promiseOfResult);
}
Name of Handler is addInterest.
Any pointers on what could be happening here?
java.nio.channels.ClosedChannelException
This means you have closed the channel and then continued to use it.

Resources