Response from a POST request in Groovy RESTClient is missing data - groovy

I am using groovy RESTClient 0.6 to make a POST request. I expect an XML payload in the response. I have the following code:
def restclient = new RESTClient('<some URL>')
def headers= ["Content-Type": "application/xml"]
def body= getClass().getResource("/new_resource.xml").text
/*
If I omit the closure from the following line of code
RESTClient blows up with an NPE..BUG?
*/
def response = restclient.post(
path:'/myresource', headers:headers, body:body){it}
println response.status //prints correct response code
println response.headers['Content-Length']//prints 225
println response.data //Always null?!
The response.data is always null, even though when I try the same request using Google chrome's postman client, I get back the expected response body. Is this a known issue with RESTClient?

The HTTP Builder documentation says that data is supposed to contain the parsed response content but, as you've discovered, it just doesn't. You can, however, get the parsed response content from the reader object. The easiest, most consistent way I've found of doing this is to set the default success and failure closures on your RESTClient object like so:
def restClient = new RESTClient()
restClient.handler.failure = { resp, reader ->
[response:resp, reader:reader]
}
restClient.handler.success = { resp, reader ->
[response:resp, reader:reader]
}
You'll get the same thing on success and failure: a Map containing the response (which is an instance of HttpResponseDecorator) and the reader (the type of which will be determined by the content of the response body).
You can then access the response and reader thusly:
def map = restClient.get([:]) // or POST, OPTIONS, etc.
def response = map['response']
def reader = map['reader']
assert response.status == 200

I faced a similar issue and I took the cue from Sams solution but used closures to address it (similar solution but coded using closures instead of the returned object).
resp.data is always null when using the RESTClient, however the reader contains the data, so it would look something like this:
def restclient = new RESTClient('<some URL>')
def headers= ["Content-Type": "application/xml"]
def body= getClass().getResource("/new_resource.xml").text
try {
restclient.post(path:'/myresource', headers:headers, body:body) { resp, reader ->
resp.status // Status Integer
resp.contentType // Content type String
resp.headers // Map of headers
resp.data // <-- ALWAYS null (the bug you faced)
reader // <-- Data you're looking for
}
} catch (Exception e) {
e.response.status // Get HTTP error status Integer
}

Related

How can I get the request body as Json while also getting the cookie in Actix-web?

I'm a college student, currently trying to rewrite a toy Django website using Actix-web, and I'm facing problems.
The original Python code looks like this(irrelevant parts removed). It receives a request as the parameter, extract the 'title' and 'content' field, and get the user name from cookies.
def message(request):
def gen_response(code: int, data: str):
return JsonResponse({
'code': code,
'data': data
}, status=code)
if request.method == 'POST':
name = request.COOKIES['user'] if 'user' in request.COOKIES else 'Unknown'
user = User.objects.filter(name=name).first()
try:
request_body = json.loads(request.body)
except JSONDecodeError as e:
return gen_response(400, "Json Parse Error: {}".format(e))
return gen_response(201, "message was sent successfully")
else:
return gen_response(405, 'method {} not allowed'.format(request.method))
Now I want my Rust code to run just like that. If I use web:Json<PostData> as the parameter, I don't know how to read the cookies. And if I use the raw HttpRequest, I can't see how I should extract the request body
HttpRequest have a method called cookie.And Cookie exist in HttpHeader.So you can use headers() get cookie.
/// cookie = "0.14"
use cookie::Cookie;
#[post("/cookie")]
async fn test_cookie(post: Json<HashMap<String, String>>, request: HttpRequest) -> HttpResponse {
let x:Cookie = request.cookie("prov").unwrap();
println!("{:?}", x.value());
let head = request.headers().get("cookie").unwrap();
println!("{}", head.to_str().unwrap());
HttpResponse::Ok().body("")
}

Can't extract data from RESTClient response

I am writing my first Groovy script, where I am calling a REST API.
I have the following call:
def client = new RESTClient( 'http://myServer:9000/api/resources/?format=json' )
That returns:
[[msr:[[data:{"level":"OK"}]], creationDate:2017-02-14T16:44:11+0000, date:2017-02-14T16:46:39+0000, id:136]]
I am trying to get the field level, like this:
def level_value = client.get( path : 'msr/data/level' )
However, when I print the value of the variable obtained:
println level_value.getData()
I get the whole JSON object instead of the field:
[[msr:[[data:{"level":"OK"}]], creationDate:2017-02-14T16:44:11+0000, date:2017-02-14T16:46:39+0000, id:136]]
So, what am I doing wrong?
Haven't looked at the docs for RESTClient but like Tim notes you seem to have a bit of a confusion around the rest client instance vs the respons object vs the json data. Something along the lines of:
def client = new RESTClient('http://myServer:9000/api/resources/?format=json')
def response = client.get(path: 'msr/data/level')
def level = response.data[0].msr[0].data.level
might get you your value. The main point here is that client is an instance of RESTClient, response is a response object representing the http response from the server, and response.data contains the parsed json payload in the response.
You would need to experiment with the expression on the last line to pull out the 'level' value.

How to call REST from jenkins workflow

I wonder how to call REST API from a (groovy) Jenkins workflow script. I can execute "sh 'curl -X POST ...'" - it works, but building the request as a curl command is cumbersome and processing the response gets complicated. I'd prefer a native Groovy HTTP Client to program in groovy - which one should I start with? As the script is run in Jenkins, there is the step of copying all needed dependency jars to the groovy installation on Jenkins, so something light-weight would be appreciated.
Native Groovy Code without importing any packages:
// GET
def get = new URL("https://httpbin.org/get").openConnection();
def getRC = get.getResponseCode();
println(getRC);
if(getRC.equals(200)) {
println(get.getInputStream().getText());
}
// POST
def post = new URL("https://httpbin.org/post").openConnection();
def message = '{"message":"this is a message"}'
post.setRequestMethod("POST")
post.setDoOutput(true)
post.setRequestProperty("Content-Type", "application/json")
post.getOutputStream().write(message.getBytes("UTF-8"));
def postRC = post.getResponseCode();
println(postRC);
if(postRC.equals(200)) {
println(post.getInputStream().getText());
}
There is a built in step available that is using Jenkins HTTP Request Plugin to make http requests.
Plugin: https://wiki.jenkins-ci.org/display/JENKINS/HTTP+Request+Plugin
Step documentation: https://jenkins.io/doc/pipeline/steps/http_request/#httprequest-perform-an-http-request-and-return-a-response-object
Example from the plugin github page:
def response = httpRequest "http://httpbin.org/response-headers?param1=${param1}"
println('Status: '+response.status)
println('Response: '+response.content)
I had trouble installing the HTTPBuilder library, so I ended up using the more basic URL class to create an HttpUrlConnection.
HttpResponse doGetHttpRequest(String requestUrl){
URL url = new URL(requestUrl);
HttpURLConnection connection = url.openConnection();
connection.setRequestMethod("GET");
//get the request
connection.connect();
//parse the response
HttpResponse resp = new HttpResponse(connection);
if(resp.isFailure()){
error("\nGET from URL: $requestUrl\n HTTP Status: $resp.statusCode\n Message: $resp.message\n Response Body: $resp.body");
}
this.printDebug("Request (GET):\n URL: $requestUrl");
this.printDebug("Response:\n HTTP Status: $resp.statusCode\n Message: $resp.message\n Response Body: $resp.body");
return resp;
}
/**
* Posts the json content to the given url and ensures a 200 or 201 status on the response.
* If a negative status is returned, an error will be raised and the pipeline will fail.
*/
HttpResponse doPostHttpRequestWithJson(String json, String requestUrl){
return doHttpRequestWithJson(json, requestUrl, "POST");
}
/**
* Posts the json content to the given url and ensures a 200 or 201 status on the response.
* If a negative status is returned, an error will be raised and the pipeline will fail.
*/
HttpResponse doPutHttpRequestWithJson(String json, String requestUrl){
return doHttpRequestWithJson(json, requestUrl, "PUT");
}
/**
* Post/Put the json content to the given url and ensures a 200 or 201 status on the response.
* If a negative status is returned, an error will be raised and the pipeline will fail.
* verb - PUT or POST
*/
HttpResponse doHttpRequestWithJson(String json, String requestUrl, String verb){
URL url = new URL(requestUrl);
HttpURLConnection connection = url.openConnection();
connection.setRequestMethod(verb);
connection.setRequestProperty("Content-Type", "application/json");
connection.doOutput = true;
//write the payload to the body of the request
def writer = new OutputStreamWriter(connection.outputStream);
writer.write(json);
writer.flush();
writer.close();
//post the request
connection.connect();
//parse the response
HttpResponse resp = new HttpResponse(connection);
if(resp.isFailure()){
error("\n$verb to URL: $requestUrl\n JSON: $json\n HTTP Status: $resp.statusCode\n Message: $resp.message\n Response Body: $resp.body");
}
this.printDebug("Request ($verb):\n URL: $requestUrl\n JSON: $json");
this.printDebug("Response:\n HTTP Status: $resp.statusCode\n Message: $resp.message\n Response Body: $resp.body");
return resp;
}
class HttpResponse {
String body;
String message;
Integer statusCode;
boolean failure = false;
public HttpResponse(HttpURLConnection connection){
this.statusCode = connection.responseCode;
this.message = connection.responseMessage;
if(statusCode == 200 || statusCode == 201){
this.body = connection.content.text;//this would fail the pipeline if there was a 400
}else{
this.failure = true;
this.body = connection.getErrorStream().text;
}
connection = null; //set connection to null for good measure, since we are done with it
}
}
And then I can do a GET with something like:
HttpResponse resp = doGetHttpRequest("http://some.url");
And a PUT with JSON data using something like:
HttpResponse resp = this.doPutHttpRequestWithJson("{\"propA\":\"foo\"}", "http://some.url");
Have you tried Groovy's HTTPBuilder Class?
For example:
#Grapes(
#Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.7.1')
)
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*
def http = new HTTPBuilder("http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo")
http.request(POST, JSON ) { req ->
body = []
response.success = { resp, reader ->
println "$resp.statusLine Respond rec"
}
}
Blocking the main thread on I/O calls is not a good idea.
Delegating the I/O operation to a shell step is the recommended way currently.
The other way, which requires development, is to add a new step. By the way, there is an initiative to add a common set of steps to be used securely inside the pipeline script, although a full REST client owes its own plugin.
Do a GET with the Basic Auth header.
def accessToken = "ACCESS_TOKEN".bytes.encodeBase64().toString()
def req = new URL("https://raw.githubusercontent.com/xxxx/something/hosts").openConnection();
req.setRequestProperty("Authorization", "Basic " + accessToken)
def content = req.getInputStream().getText()

HTTPBuilder Not Acceptable error

I am getting this error but my method looks perfectly fine to me
def http = new HTTPBuilder()
http.request(
'https://textalytics-topics-extraction-11.p.mashape.com/topics-1.2?txt=ben',
Method.GET,
ContentType.JSON
) { req ->
headers."X-Mashape-Key" = "mashkey"
response.success = { resp, reader ->
assert resp.statusLine.statusCode == 200
println "Got response: ${resp.statusLine}"
println "Content-Type: ${resp.headers.'Content-Type'}"
println reader.text
}
response.'404' = {
println 'Not found'
}
}
any ideas?
Given that HTTP Not acceptable means:
406 Not Acceptable
The resource identified by the request is only capable of generating
response entities which have content characteristics not acceptable
according to the accept headers sent in the request.
Then perhaps their API is finicky and you need to specify that you want the response to be JSON using the Accept header, using something like Accept: application/json.

Groovy RESTClient returns inconsistent type on response handler

I am attempting to write my own response handlers for Groovy's RESTClient (which wraps around HttpBuilder). I want to always print the response body if one is returned. However, I cannot find a consistent way to do it.
Typically a custom response handler would look like this:
def client = new RESTClient(url)
client.handler.success = { resp, reader ->
//do stuff
}
client.handler.failure = { resp, reader ->
//do stuff
throw new Exception("HTTP call failed. Status code: ${resp.getStatus()}")
}
However, what I noticed is that the variable "reader" can have a different class depending on the response. I've seen the reader be of type groovy.util.slurpersupport.NodeChild or org.apache.http.conn.EofSensorInputStream. I want it to be a predictable class so I can actually call the methods on this object. What's going on here?
Setting the content type to ANY and changing the HttpBuilder content parsers to the text parser fixed the issue. The type of reader in the response handler is now always java.io.InputStreamReader.
Before:
def headerMap = [:]
//populate headers
def response = client.get("headers":headerMap)
After:
client.parser.'application/xml' = client.parser.'text/plain'
client.parser.'application/xhtml+xml' = client.parser.'text/plain'
client.parser.'application/atom+xml' = client.parser.'text/plain'
client.parser.'application/json' = client.parser.'text/plain'
client.parser.'text/html' = client.parser.'text/plain'
client.parser.'application/x-www-form-urlencoded' = client.parser.'text/plain'
def headerMap = [:]
//populate headers
def response = client.get("headers":headerMap, contentType:groovyx.net.http.ContentType.ANY)

Resources