HTTPBuilder gets 403 while cURL works fine (GitHub API) - groovy

A GET request sent to https://api.github.com/users/username works from the command line and via URL#text, but fails using HTTPBuilder.
The code:
new HTTPBuilder('https://api.github.com').get(path: '/users/xan', contentType: JSON) // fails
"https://api.github.com/users/xan".toURL().text // works
On the command line:
# works:
$ curl https://api.github.com/users/xan
Also a spock test is available in this gist
Why?

Eventually I found out: GitHub denies access if the User-Agent header is missing.
This works:
def http = new HTTPBuilder('https://api.github.com')
def response = http.get(path: '/users/qmetric',
headers: [(USER_AGENT): "Apache HTTPClient"])

Because you must accept the good conten type aswell.
i.e.
def http = new HTTPBuilder('http://ajax.googleapis.com')
http.request( Method.GET, ContentType.TEXT ) { req ->
uri.path = '/ajax/services/search/web'
uri.query = [ v:'1.0', q: 'Calvin and Hobbes' ]
headers.Accept = 'application/json'
response.success = { resp, reader ->
println "Got response: ${resp.statusLine}"
println "Content-Type: ${resp.headers.'Content-Type'}"
print reader.text
}
}

Related

How to write the correct recipe for an HTTP POST request on Groovy

I'm trying to write a recipe for an HTTP POST request in Groovy and the way I am doing it is by editing a previous recipe but adjusting it to my own needs:
Set two request properties (Content-Type: application/json, Authorization: Bearer xxxxx)
Set raw data (json) ({"var_1": "value 1", "var_2": "value 2"})
The recipe I'm trying to modify is
def baseUrl = new URL('http://some.url.com/')
def connection = baseUrl.openConnection()
connection.with {
doOutput = true
requestMethod = 'POST'
setRequestProperty = ['Content-Type': 'application/json', 'Authorization': 'Bearer xxxx']
outputStream.withWriter { writer ->
writer
}
println content.text
}
At first when executing the script I get the following error:
Exception thrown
java.io.IOException: Server returned HTTP response code: 401 for URL: http://some.url.com/
The other thing is that I don't know how to add the need (2) to the script.
Thanks in advance.
Based on Javadoc I would put the code like so:
def baseUrl = new URL('http://some.url.com/')
def connection = baseUrl.openConnection()
connection.with {
doOutput = true
requestMethod = 'POST'
addRequestProperty 'Content-Type', 'application/json'
addRequestProperty 'Authorization', 'Bearer xxxx'
outputStream.withWriter{ it << '{"var_1": "value 1", "var_2": "value 2"}' }
println content.text
}

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.

How can I execute a simple request with an URL in Groovy/Java?

I have the following code in groovy:
def http = new HTTPBuilder( 'http://localhost:8080' )
http.post( path: '/this/is/my/path/'+variable) { resp ->
println "POST Success: ${resp.statusLine}"
assert resp.statusLine.statusCode == 200
}
I only want to execute that request. I have a method in another application that when there is a request in that url, I see a result. Problem is that I see nothing.
What might be the problem?
Most likely, your application only responds to GET request and not to POST requests. Try GET instead:
def http = new HTTPBuilder( 'http://localhost:8080' )
http.get( path: '/this/is/my/path/'+variable) { resp ->
println "GET Success: ${resp.statusLine}"
assert resp.statusLine.statusCode == 200
}
Also, are you sure that you expect a HTTP status 201 (Created) at this URL?
Could try just opening a simple HttpURLConnection like this:
URL url = new URL("http://localhost:8080/this/is/my/path/${variable}")
HttpURLConnection connection = url.openConnection()
println "responseCode: ${connection.responseCode}"
assert connection.responseCode == 200

Response from a POST request in Groovy RESTClient is missing data

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
}

Resources