Retrofit2 + SimpleXML + SOAP request in Kotlin - soap

I'm trying to create a service in the Android App that consumes a SOAP API. Sent values and returned values are XML.
Previously i used FormUrlEncoded + JSON in another API and worked, but with XML i'm struggling as the API seems that is not being called (HttpLoggingInterceptor don't show and also the Mockup service don't show any petition).
If i change to FormUrlEncoded my service i can see that the request is done (i checked it with HttpLoggingInterceptor, but if i remove the FormUrlEncoded seems like service is not called never.
My NetModule where is create the retrofir, parser, etc:
#Module
class NetModule {
#Provides
#Singleton
fun provideRetrofit(): Retrofit {
val client =
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
val strategy = AnnotationStrategy()
val serializer = Persister(strategy)
return Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.client(client)
.addConverterFactory(SimpleXmlConverterFactory.create(serializer))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
#Provides
#Singleton
fun provideFilesService(retrofit: Retrofit): FilesService =
retrofit.create(FilesService::class.java)
}
My FilesService.kt where the interface is defined is:
import com.liderasoluciones.enviotest.data.model.FileSendResponse
import com.liderasoluciones.enviotest.data.model.FileSendEnvelope
import io.reactivex.Flowable
import retrofit2.http.*
interface FilesService {
#Headers(
"Content-Type: application/soap+xml",
"Accept-Charset: utf-8"
)
#POST("mockWSSMTSoap")
fun sendFile(#Body body: FileSendEnvelope): Flowable<FileSendResponse>
}
My model for the Body, Request and data is FileSendEnvelope.kt and is:
import org.simpleframework.xml.Element
import org.simpleframework.xml.Root
import org.simpleframework.xml.Namespace;
import org.simpleframework.xml.NamespaceList;
#Root(name = "GetInfoByState", strict = false)
#Namespace(reference = "http://www.webservicetest.net")
class FileSendData {
#Element(name = "FileName", required = false)
var name: String? = null
}
#Root(name = "soap12:Body", strict = false)
class FileSendBody {
#Element(name = "GetInfoByFile", required = false)
var fileSendData: FileSendData? = null
}
#Root(name = "soap12:Envelope")
#NamespaceList(
Namespace(prefix = "xsi", reference = "http://www.w3.org/2001/XMLSchema-instance"),
Namespace(prefix = "xsd", reference = "http://www.w3.org/2001/XMLSchema"),
Namespace(prefix = "soap12", reference = "http://www.w3.org/2003/05/soap-envelope")
)
class FileSendEnvelope {
#Element(name = "soap12:Body", required = false)
var body: FileSendBody? = null
}
From the RemoteDataSource class is where i call the api:
class RemoteFilesDataSource(private val filesService: FilesService,
private val genericResponseEntityMapper: GenericResponseEntityMapper):
FilesDataSource {
override fun sendFile(userToken: String): Flowable<GenericResponseEntity> {
var petitionEnvelope = FileSendEnvelope()
var petitionBody = FileSendBody()
var petitionData = FileSendData()
petitionData.name = "test.png"
petitionBody.fileSendData = petitionData
petitionEnvelope.body =
return filesService.sendFile(petitionEnvelope)
.map { it.result }
.map { genericResponseEntityMapper.transform(it) }
}
}
At this moment i'm not taking so much care about the XML sent or parse the response, i just "want to check" that the API is called.
I tried to follow this info:
https://github.com/asanchezyu/RetrofitSoapSample
http://geekcalledk.blogspot.com/2014/08/use-simple-xml-with-retrofit-for-making.html
Even are java examples and i'm using Kotlin but no luck.
Any help is appreciated.
Thanks in advance.

Have you tried to use text/xml for your Content-type in your header? (and try it without the Accept-Charset header as well)
#Headers(
"Content-type: text/xml"
)

Related

Modify contentHeaders of Swagger Codegen methods in kotlin

I'm using swagger codegen for my REST API calls. For authentication purposes i need to send a session-token within the headers of every request. This is currently done, via APIClients' defaultHeaders
open class ApiClient(val baseUrl: String) {
companion object {
...
#JvmStatic
var defaultHeaders: Map<String, String> by ApplicationDelegates.setOnce(mapOf(ContentType to JsonMediaType, Accept to JsonMediaType))
...
}
}
The way swagger generates the code, these headers can only be modified once.
ApiClient.defaultHeaders += mapOf("Authorization" to userSession!!.idToken.jwtToken)
The problem with this is, that i cannot change the token (e.g. because another user logged in within the application lifetime). Looking deeper into the generated code, before each request is sent, a merge of both defaultHeaders and requestConfig.headers (=contentHeaders) is being made.
inline protected fun <reified T: Any?> request(requestConfig: RequestConfig, body : Any? = null): ApiInfrastructureResponse<T?> {
...
val headers = defaultHeaders + requestConfig.headers
...
}
The given RequestConfig object comes from every api call. However it is not possible to change these contentHeaders. Also they are empty by default.
fun someAPIRestCall(someParam: kotlin.String) : Unit {
val localVariableBody: kotlin.Any? = type
val localVariableQuery: MultiValueMap = mapOf()
val contentHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf() // THESE WILL BE MERGED WITH defaultHeaders
val acceptsHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf("Accept" to "application/json")
val localVariableHeaders: kotlin.collections.MutableMap<kotlin.String,kotlin.String> = mutableMapOf()
localVariableHeaders.putAll(contentHeaders)
localVariableHeaders.putAll(acceptsHeaders)
val localVariableConfig = RequestConfig(
RequestMethod.POST,
"someEndpointURL"),
query = localVariableQuery,
headers = localVariableHeaders // THESE WILL BE MERGED WITH defaultHeaders
)
val response = request<Unit>(
localVariableConfig,
localVariableBody
)
...
}
Is it possible to tell swagger-codegen to include some kind of parameter to the generated method signature to add values to those contentHeaders?
EDIT:
This is the current code-gen call within my gradle build chain
task generateSwagger(type: JavaExec) {
main = "-jar"
args "swagger-codegen-cli-2.4.7.jar", "generate", "-i", "./swagger_core.yml", "-l", "kotlin", "-o", "./tmp/RestApi", "--type-mappings", "number=kotlin.Long"
}
By now, i found a solution, that is more of a hack, but it works.
As i am using gradle to build the app, i introduced a task, that changes the generated swagger code, before it actually compiles.
task editAPISources {
def token = "Map<String, String> by ApplicationDelegates.setOnce(mapOf(ContentType to JsonMediaType, Accept to JsonMediaType))"
def value = "MutableMap<String, String> = mutableMapOf(ContentType to JsonMediaType, Accept to JsonMediaType)"
def file = new File("./app/tmp/RestApi/src/main/kotlin/io/swagger/client/infrastructure/ApiClient.kt")
def newConfig = file.text.replace(token, value)
file.write newConfig
}
The result is a now changeable header :=
#JvmStatic
var defaultHeaders: MutableMap<String, String> = mutableMapOf(ContentType to JsonMediaType, Accept to JsonMediaType)

GET img/png with grails.plugins.rest.client.RestResponse

I am using grails.plugins.rest.client.RestResponse to GET a binary resource of content-type png from a rest endpoint.
import grails.plugins.rest.client.RestBuilder
import grails.plugins.rest.client.RestResponse
String url = "http://localhost:8080/rest-endpoint/image.png"
RestBuilder rest = new RestBuilder()
RestResponse restResponse = rest.get(url){
auth username, password
accept "image/png"
contentType "image/png"
}
byte[] png_image = restResponse.responseEntity.body
println "length " + png_image.length
I'm not sure why the length that is returned is ~500bytes less than expected and I have tried with a different image and a different url and the length returned is lower everytime. Any idea why?
I had the same problem and ended up using HTTPBuilder instead.
import groovyx.net.http.ContentType
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method
class RestUtil {
static byte[] getBytes(String url) {
new HTTPBuilder(url).request(Method.GET, ContentType.BINARY) {
requestContentType = ContentType.BINARY
response.success = { resp, binary ->
return binary.bytes
}
}
}
}
Hope that's helpful.

encoder function for multipart/form-data in groovy

I need to form a 'multipart/form-data' REST request with jpeg image and JSON file as the content.I am stuck with encoding the 'multipart/form-data' as a zip file.
Can someone tell me, how I can achieve this with groovy RESTClient? I could not find any documentation regarding this.
As it can be seen in the docs RESTClient extends HTTPBuilder. HTTPBuilder has a getEncoder method that can be used to add dedicated encoder (with type and method). See the following piece of code:
import org.codehaus.groovy.runtime.MethodClosure
import javax.ws.rs.core.MediaType
//this part adds a special encoder
def client = new RESTClient('some host')
client.encoder.putAt(MediaType.MULTIPART_FORM_DATA, new MethodClosure(this, 'encodeMultiPart'))
//here is the method for the encoder added above
HttpEntity encodeMultiPart(MultipartBody body) {
MultipartEntityBuilder.create()
.addBinaryBody(
'file',
body.file,
ContentType.MULTIPART_FORM_DATA,
body.filename
).build()
}
//here's how MultipartBody class looks:
class MultipartBody {
InputStream file
String filename
}
Now to create a multipart request You need to pass an instance of MultipartBody as a body argument to the request.
I was writing test using Groovy rest client to upload a .zip file.
None of the above answer's worked for me directly when testing with Groovy Rest Client. I had to make some adjustsment to the above answers. I am posting here so that some-one wants to post using Groovy Rest client can get benefits.
import groovyx.net.http.RESTClient
import org.apache.http.HttpEntity
import org.apache.http.entity.mime.MultipartEntityBuilder
import org.codehaus.groovy.runtime.MethodClosure
import static groovyx.net.http.ContentType.JSON
def uploadFile() {
def httpClient = new RESTClient(this.host)
File fileToUpload = new File("src/test/resources/fileName.zip")
httpClient.encoder.putAt(javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA, new MethodClosure(this, 'encodeMultiPart'))
def multipartBody = new MultipartBody()
multipartBody.file = new FileInputStream(fileToUpload)
multipartBody.filename = fileToUpload.name
def response = httpClient.post(
path: '/app/uploadfile/path',
headers: [Accept : JSON,
User : "user",
Password: "password"
],
body: multipartBody,
requestContentType: 'multipart/form-data')
}
// register multipart encoder
HttpEntity encodeMultiPart(MultipartBody body) {
MultipartEntityBuilder.create()
.addBinaryBody(
'file',
body.file,
org.apache.http.entity.ContentType.MULTIPART_FORM_DATA,
body.filename
).build()
}
class MultipartBody {
InputStream file
String filename
}
Realise this is an oldy but might help others, although the question answers it from a beginner point of view it is difficult to fully understand how to reuse all of above properly.
Firstly the last comment on the question points to this link :
Which attempts to re-use the answer incorrectly. It has mixed above answer with an answer from this link
def content1 = new ContentDisposition("filename=aa.json")
def json1 = new File("resources/aa.json")
def attachments1 = new Attachment("root", new ByteArrayInputStream(json1.getBytes()), content1)
InputStream is2 = getClass().getResourceAsStream("resources/aa.json");
InputStream is1 = getClass().getResourceAsStream("resources/img.png");
ContentDisposition content2 = new ContentDisposition("attachment;filename=img.png")
Attachment attachments2 = new Attachment("root1", is1, content2)
def attachments = [attachments1, attachments2]
def body1 = new MultipartBody(attachments)
def client = new RESTClient( "https://somehost.com" )
ocutag.encoder.putAt(MediaType.MULTIPART_FORM_DATA, new MethodClosure(this, 'encodeMultiPart1'))
ocutag.encoder.putAt(MediaType.MULTIPART_FORM_DATA, new MethodClosure(this, 'encodeMultiPart2'))
The above is never going to work, I have it working like so:
def http = new RESTClient('http://localhost:8080')
http.encoder.putAt(MediaType.MULTIPART_FORM_DATA, new MethodClosure(this, 'encodeMultiPart'))
def body1 = new MultipartBody() //This is that MultipartBody class in the first answer example not the one from your imports......
body1.file=file.getInputStream()
body1.filename=file.name
def response = http.put( path: url, body:body1, query:['query':action, ], requestContentType: 'multipart/form-data' )
You also have encodeMultiPart2 and encodeMultiPart1, I think this is a misunderstanding just reuse 1 declaration of this method in both cases.. you don't need to do none of the attachments etc you have in your example..
Encoder registrations are so messy in previous responses, here is my working example:
import org.apache.cxf.jaxrs.ext.multipart.Attachment
import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition
import org.apache.cxf.jaxrs.ext.multipart.MultipartBody
import org.apache.http.entity.ContentType
import org.apache.http.entity.mime.MultipartEntityBuilder
import javax.ws.rs.core.MediaType
...
def filenameToUpload = "doggo.jpg"
def expectedRequestParamName = "file"
def static uploadFile() {
// create attachment
def fileToUpload = new File(filenameToUpload)
def attachment = new Attachment(expectedRequestParamName, new ByteArrayInputStream(fileToUpload.getBytes()), new ContentDisposition("filename=" + filenameToUpload))
def body = new MultipartBody(attachment)
// create REST client
def httpClient = new RESTClient('http://localhost:8080')
// register encoder
httpClient.encoder.putAt(MediaType.MULTIPART_FORM_DATA, customMultipartEncoder)
// call REST
httpClient.post(
path: "upload",
body: body,
requestContentType: MediaType.MULTIPART_FORM_DATA)
}
// register multipart encoder
private def static customMultipartEncoder = { body ->
def builder = MultipartEntityBuilder.create()
body.allAttachments.collect {
builder.addBinaryBody(
it.contentId,
it.dataHandler.inputStream,
ContentType.MULTIPART_FORM_DATA,
it.contentId) }
return builder.build()
}

How to PUT XmlSlurper back to REST with HttpBuilder

I'm trying to make GET and then PUT call on XML REST web service.
I do it this way:
#Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7')
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*
import groovy.xml.XmlUtil
def url = "http://localhost:81"
def pathPrefix = "/api/v1"
def http = new HTTPBuilder(url)
def profile = http.request(GET, XML) { req ->
uri.path = "$pathPrefix/profiles/55"
response.success = {resp, xml ->
xml
}
}
println XmlUtil.serialize(profile) // this is fine!
Now i'm going to change and save
profile.name = "New Name"
// this is not fine (i have 400 Bad Request)
// because it sends body not in XML
def savedProfile = http.request(PUT, XML) { req ->
uri.path = "$pathPrefix/profiles/55"
body = profile
response.success = {resp, xml ->
xml
}
}
println XmlUtil.serialize(savedProfile)
When i make PUT request HTTPBuilder do not send XML. It sends string, made of profile.toString().
It it not what i'm expecting.
How to send XmlSlurper object (that i obtained earlier) in PUT request?
Thank you.
I think i found the solution.
When i define body configuration value, i have to write
body = {
mkp.yield profile
}

Gradle Script To call a REST Web service without any 3rd party plugins, any pointers?

Hi I need to call a REST service as part of the buildscript (Gradle) without any 3rd party plugins, how could I use Groovy to do that?
(My first attempt)
repositories {
mavenCentral()
}
dependencies {
complie "org.codehaus.groovy.modules.http-builder:http-builder:0.5.2"
}
task hello {
def http = new HTTPBuilder("http://myserver.com:8983/solr/select?q=*&wt=json")
http.auth.basic 'username', 'password'
http.request(GET, JSON ) { req ->
}
}
Can't you just do
new URL( 'http://username:password#myserver.com:8983/solr/select?q=*&wt=json' ).text
this is working guys
import java.io.*
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.EncoderRegistry
import static groovyx.net.http.Method.*
import static groovyx.net.http.ContentType.*
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.codehaus.groovy.modules.http-builder:http-builder:0.5.2'
}
}
task hello {
def http = new groovyx.net.http.HTTPBuilder("http://local.com:8983/solr/update/json")
http.request(POST, JSON ) { req ->
req.body{
}
response.success = { resp, reader ->
println "$resp.statusLine Respond rec"
}
}
}
This question is ranking so well on search engines that I keep stumbling on it.
However, as others commented, I don't really like the accepted answer because it relies on curl.
So here is a complete example w/o any prerequisite (no plugin, no curl, ...):
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
task getExample {
doLast {
def req = new URL('https://jsonplaceholder.typicode.com/posts/1').openConnection()
logger.quiet "Status code: ${req.getResponseCode()}"
def resp = new JsonSlurper().parseText(req.getInputStream().getText())
logger.quiet "Response: ${resp}"
}
}
task postExample {
doLast {
def body = [title: "foo", body: "bar", userId: 1]
def req = new URL('https://jsonplaceholder.typicode.com/posts').openConnection()
req.setRequestMethod("POST")
req.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
req.setDoOutput(true)
req.getOutputStream().write(JsonOutput.toJson(body).getBytes("UTF-8"))
logger.quiet "Status code: ${req.getResponseCode()}" // HTTP request done on first read
def resp = new JsonSlurper().parseText(req.getInputStream().getText())
logger.quiet "Response: ${resp}"
}
}
You can run this on your box as they use a public development API.
The easiest way to call REST from groovy without external libraries is executing CURL. Here's an example of calling Artifactory, getting JSON back and parsing it:
import groovy.json.JsonSlurper
task hello {
def p = ['curl', '-u', '"admin:password"', "\"http://localhost:8081/api/storage/libs-release-local?list&deep=1\""].execute()
def json = new JsonSlurper().parseText(p.text)
}
I'm using the JsonSlurper it looks quite simple and OS independent:
import groovy.json.JsonSlurper
String url = "http://<SONAR_URL>/api/qualitygates/project_status?projectKey=first"
def json = new JsonSlurper().parseText(url.toURL().text)
print json['projectStatus']['status']
Java has had a proper HTTP Client in the standard library since Java 9.
Here's how you can use that from a Gradle script (Groovy):
import groovy.json.JsonSlurper
import java.net.http.*
import static java.nio.charset.StandardCharsets.UTF_8
tasks.register('hello') {
def url = new URL("http://myserver.com:8983/solr/select?q=*&wt=json")
def req = HttpRequest.newBuilder(url.toURI()).GET().build()
def res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString(UTF_8))
def parsedJson = new JsonSlurper().parseText(res.body())
println parsedJson
}
The solution using Kotlin DSL and Fuel HTTP Client:
import com.github.kittinunf.fuel.httpPost
import com.github.kittinunf.result.Result
buildscript {
dependencies {
"classpath"("com.github.kittinunf.fuel:fuel:2.3.1")
}
}
tasks {
register("post") {
val (request, response, result) = "https://httpbin.org/post".httpPost().responseString()
if (result is Result.Success) {
println(result.get())
}
}
}