Logo

dev-resources.site

for different kinds of informations.

Graalvanizando un script de Groovy

Published at
4/22/2023
Categories
java
groovy
graalvm
linkedin
Author
jagedn
Categories
4 categories in total
java
open
groovy
open
graalvm
open
linkedin
open
Author
6 person written this
jagedn
open
Graalvanizando un script de Groovy

En este post voy a publicar el código y pasos a seguir para convertir un script de Groovy en un binario de tal forma que para su ejecución no se necesita ni Groovy ni Java instalado. Además, aunque es simple, la velocidad de ejecución es sensiblemente mejor

En concreto el aplicativo va a subir un post a Linkedin (por ahora texto, luego veré de adjuntar imágenes) usando el nuevo api REST de esta plataforma. Para ello pedirá el author y el accessToken para poder publicar

Requisitos

Para completar todos los pasos se requiere tener instado:

Una vez instalado le indicaremos que nos instale las versiones de Java y Groovy necesarias para generar el binario:

sdk use groovy 4.0.11

sdk use java 22.3.r19-grl

Configuración

Para generar una imagen nativa de un script de groovy es necesario que este sea compilado en modo estático (lo que resta mucho de la expresividad de Groovy, pero …​)

compiler.config

withConfig(configuration) {
    ast(groovy.transform.CompileStatic)
    ast(groovy.transform.TypeChecked)
}
Enter fullscreen mode Exit fullscreen mode

GroovyScript

Para mejorar la legibilidad del código he dividido el script en 2 ficheros .groovy, uno de ellos con métodos estáticos orientado a hacer las peticiones HTTP get y post y el otro que será el que contenga la "logica de negocio"

HttpUtil.groovy

import groovy.json.JsonOutput
import groovy.json.JsonSlurper

import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse

class HttpUtil{

    static Map postJson(Map map, String url, String token) {
        println "-> ${JsonOutput.prettyPrint(JsonOutput.toJson(map).toString())}"
        def request = HttpRequest.newBuilder(new URI(url))
                .headers(
                        "Content-Type", "application/json",
                        "X-Restli-Protocol-Version", "2.0.0",
                        "LinkedIn-Version", "202301",
                        "Authorization", "Bearer $token"
                )
                .POST(HttpRequest.BodyPublishers.ofString(JsonOutput.toJson(map).toString()))
                .build()
        def response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofInputStream())
        if( response.statusCode() > 299 ){
            println response
            return null
        }
        def result = response.body().text
        println "<-${result}"
        if( !result ){
            return null
        }
        new JsonSlurper().parseText(result) as Map
    }

    static Map getJson(String url, String token){
        def request = HttpRequest.newBuilder(new URI(url))
                .headers(
                        "Content-Type", "application/json",
                        "X-Restli-Protocol-Version", "2.0.0",
                        "LinkedIn-Version", "202301",
                        "Authorization", "Bearer $token"
                )
                .GET()
                .build()
        def response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString())

        def result = response.body()
        println "<-${result}"
        new JsonSlurper().parseText(result) as Map
    }
}
Enter fullscreen mode Exit fullscreen mode

Aunque parece tener mucho código en realidad es una implementación básica para usar las clases Http de Java y poder hacer un get y un post con unas cabeceras determinadas así como enviar y recibir Mapas como si fueran Json.

PostContent.groovy

@GrabConfig(systemClassLoader = true)
@Grab('info.picocli:picocli-groovy:4.7.3')

@picocli.groovy.PicocliScript2

import groovy.transform.Field
import static picocli.CommandLine.*

@Option(names = ["-a", "--author"], description = "nickname", required=true)
@Field String author = ''

@Option(names = ["-k", "--token"], description = "token", required=true)
@Field String token = ''

@Option(names = ["-t", "--test"], description = "run as test")
@Field boolean test = false

@Parameters
@Field List<String> content = []

String postURL = "https://api.linkedin.com/rest/posts"

if( test ){
        postURL="https://httpbin.org/delay/0"
}

Map post = [
        "author" : "urn:li:person:${author}",
        "commentary" : content.join(' '),
        "visibility" : "PUBLIC",
        "distribution" : [
                "feedDistribution" : "MAIN_FEED",
                "targetEntities" : [],
                "thirdPartyDistributionChannels": []
        ],
        "lifecycleState" : "PUBLISHED",
        "isReshareDisabledByAuthor": false
]

HttpUtil.postJson(post, postURL, token)
Enter fullscreen mode Exit fullscreen mode

Este script usa Picocli para facilitar el parseo de comandos. Simplemente necesita un author y un token y además podemos indicar que se ejecute en modo test (-t) con lo que usará el servidor de httpbin como backend para testearlo

Por lo demás es un "simple" post con el formato que Linkedin espera para publicar un post

Graalvm

Las fases para convertir este script en binario las he separado en:

  • Pasar de Groovy a Java usando groovyc

  • Ejecutarlo con Java en modo test para que el agente genere la configuracion de Graalvm

  • Ejecutar el native-image para generar el binario

compile.sh

#!/bin/bash
set -e

# sdk use groovy 4.0.11
# sdk use java 22.3.r19-grl

echo compiling script
groovyc --configscript=compiler.groovy -d out PostContent.groovy

CP="$CP:./out"
CP="$CP:$HOME/.sdkman/candidates/groovy/4.0.11/lib/groovy-4.0.11.jar"
CP="$CP:$HOME/.sdkman/candidates/groovy/4.0.11/lib/groovy-json-4.0.11.jar"
CP="$CP:$HOME/.sdkman/candidates/groovy/4.0.11/lib/groovy-cli-picocli-4.0.11.jar"
CP="$CP:$HOME/.groovy/grapes/info.picocli/picocli/jars/picocli-4.7.3.jar"
CP="$CP:$HOME/.groovy/grapes/info.picocli/picocli-groovy/jars/picocli-groovy-4.7.3.jar"

echo generating graalvm configuration
java -Dgroovy.grape.enable=false -agentlib:native-image-agent=config-output-dir=conf/ \
    -cp "$CP" \
    PostContent -t -a test -k test test

echo building native image
native-image -Dgroovy.grape.enable=false \
    --no-server \
    --no-fallback \
    --report-unsupported-elements-at-runtime \
    --initialize-at-build-time \
    --initialize-at-run-time=org.codehaus.groovy.control.XStreamUtils,groovy.grape.GrapeIvy \
    -H:ConfigurationFileDirectories=out/conf/ \
    --enable-url-protocols=http,https \
    -cp "$CP" \
    -H:ConfigurationFileDirectories=conf/ \
    PostContent
Enter fullscreen mode Exit fullscreen mode

Si todo va bien al final del proceso (poco más de 1 minuto) tendremos un binario postcontent que podremos ejecutar

./postcontent -a NH123123 -k A_BEARER_TOKEN_YOU_CAN_USE_MY_PREVIOUS_POST Hi this is a post from groovy script

INFO

Para obtener el token te remito a otro de mis post donde te cuento como generarlo, o si lo prefieres usar el servicio que he publicado.

Conclusion

Las nuevas versiones de Groovy y Graalvm permiten (con un poco de esfuerzo lo admito) poder crear comandos de consola binarios, lo que para mí abre la puerta a poder distribuir utilidades usando mi lenguaje favorito

Queda pendiente para un futuro post el poder adjuntar imágenes, que con el nuevo api Linkedin lo ha complicado un poco más.

graalvm Article's
30 articles in total
Favicon
Java Can Be Serverless Too: Using GraalVM for Fast Cold Starts
Favicon
Kotlin Native and GraalVM - The Story So Far
Favicon
Native Image Quick Reference — GraalVM for JDK 23 - graalvm
Favicon
GraalVM: The Swiss Army Knife of the JVM World
Favicon
Wednesday Links - Edition 2024-10-23
Favicon
Lambda function with GraalVM Native Image - Part 4 Measuring cold and warm starts using different Lambda memory settings
Favicon
Graalvm to run wasm from spring boot
Favicon
Lambda function with GraalVM Native Image - Part 3 Measuring cold and warm starts
Favicon
Wednesday Links - Edition 2024-09-25
Favicon
Lambda function with GraalVM Native Image - Part 2 How to develop and deploy Lambda function with Custom Runtime
Favicon
Lambda function with GraalVM Native Image - Part 1 Introduction to GraalVM and its native image capabilities
Favicon
Enhancing Performance with Static Analysis, Image Initialization and Heap Snapshotting
Favicon
Turbocharge Java Microservices with Quarkus and GraalVM Native Image
Favicon
Memory Management in GraalVM Native Image
Favicon
How to build spring boot native binary
Favicon
Exploring Graal: Next-Generation JIT Compilation for Java
Favicon
Registering Reflection in Quarkus Extensions
Favicon
Even more Opentelemetry!
Favicon
O quão difícil é um Hello World em Java?
Favicon
Mi experiencia con GraalVM y Spring Boot
Favicon
Ktor build with graalvm
Favicon
Wednesday Links - Edition 2023-06-28
Favicon
Wednesday Links - Edition 2023-06-21
Favicon
Graalvanizando un script de Groovy
Favicon
Wednesday Links - Edition 2023-02-22
Favicon
Wednesday Links - Edition 2023-01-11
Favicon
GraalVM: running C/C++ application safely in the Java world
Favicon
GraalVM Native Image for Mobile Development
Favicon
Wednesday Links - Edition 2022-11-02
Favicon
Wednesday Links - Edition 2022-10-19

Featured ones: