Search code examples
java.netxsltsaxon

Looking to perform HTTP POST from XSLT using either Java or .NET calls from Saxon


I work with a proprietary tool that leverages XSLT through the Saxon 10 engine.

I'm looking to find a way to send a payload from my XSL to an API endpoint. The payload is too large for a GET (which I was able to do in XSL) so I need it to be a POST.

The EXPath http-client extension is not available to me, but Saxon does allow you to call either .NET or Java functions.

I'm not a .NET developer, but I have used a variation of this code snippet below through Windows Workflow Foundation and that one submits fine. I thought maybe I could convert it to Saxon friendly XSLT to accomplish the same thing.

var url = "https://httpbin.org/post";
var client = new HttpClient;
var content = new StringContent(payload, System.Text.Encoding.UTF8,"application/json");
var response = await client.PostAsync(url, content);

What I want to do is replicate that kind of code but inside of an XSLT stylesheet using Saxon's ability to work with .NET.

Using the Saxonica documentation as a reference, along with other XSLT examples we have internally, I started writing this:

<xsl:stylesheet
  version="3.0"
  xmlns:net="clitype=System.Net.Http.HttpClient"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template name="postTest">
    
    <xsl:variable name="req" select="net:new()"/>

</xsl:template>

</xsl:stylesheet>

I didn't get much further as even this reports an error from the Saxon engine.

I also tried ChatGPT to see if it would give me some ideas. It gave me similar examples and they too all generate "Cannot find a X-argument function in ...". This was the code it gave me:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="3.0"
                xmlns:net="clitype:System.Net.Http"
                xmlns:encoding="clitype:System.Text.Encoding"
                exclude-result-prefixes="net encoding">
  
    <xsl:template match="/">
        <xsl:variable name="url" select="'https://httpbin.org/post'" />
        <xsl:variable name="client" select="net:HttpClient()" />
        
        <!-- Prepare the payload -->
        <xsl:variable name="payload" select="'nothing'" />
        <xsl:variable name="content" select="net:StringContent($payload, encoding:UTF8(), 'application/json')" />
        
        <!-- Send the POST request -->
        <xsl:variable name="response" select="net:HttpClient.PostAsync($url, $content)" />
        
        <!-- Read the response -->
        <xsl:variable name="responseBody" select="net:HttpResponseMessage.Content.ReadAsStringAsync($response)" />

        <!-- Output the response body -->
        <xsl:message>Response:</xsl:message>
        <xsl:value-of select="$responseBody"/>
    </xsl:template>

</xsl:stylesheet>

Does anyone here have any experience doing something like this within XSLT or .NET? I don't even care if it's .NET as Java is also supported in Saxon.

I really need to be able to POST a portion of my XML data to an external API, but I cannot add any additional libraries to my XSLT processor or Saxon.

Any suggestions are appreciated.


Solution

  • I fumbled some reflexive Java calls together that at least run through both Saxon EE 10.9 Java as well as .NET that do a POST request:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:BufferedReader="java:java.io.BufferedReader"
        xmlns:InputStreamReader="java:java.io.InputStreamReader"
        xmlns:OutputStream="java:java.io.OutputStream"
        xmlns:HttpURLConnection="java:java.net.HttpURLConnection"
        xmlns:URL="java:java.net.URL"
        xmlns:JString="java:java.lang.String"
        xmlns:Stream="java:java.util.stream.Stream"
        xmlns:Charset="java:java.nio.charset.Charset"
        xmlns:ByteBuffer="java:java.nio.ByteBuffer"
        xmlns:StandardCharsets="java:java.nio.charset.StandardCharsets"
        xmlns:jt="http://saxon.sf.net/java-type"
        exclude-result-prefixes="#all"
        version="3.0">
        
        <xsl:param name="payload" as="xs:string" expand-text="no">{ "Name" : "John Doe", "Age" : 42 }</xsl:param>
        
        <xsl:param name="url" as="xs:string">https://httpbin.org/post</xsl:param>
       
        <xsl:template name="xsl:initial-template" expand-text="yes">
            <xsl:variable name="uri" as="xs:anyURI" select="xs:anyURI($url)"/>
            <xsl:variable name="java-url" as="jt:java.net.URL" select="URL:new($uri)"/>
            <xsl:variable name="conn" select="URL:openConnection($java-url)"/>
            <xsl:message select="HttpURLConnection:setRequestMethod($conn, 'POST')"/>
            <xsl:message select="HttpURLConnection:setRequestProperty($conn, 'Content-Type', 'application/json; utf-8')"/>
            <xsl:message select="HttpURLConnection:setRequestProperty($conn, 'Accept', 'application/json')"/>
            <xsl:message select="HttpURLConnection:setDoOutput($conn, true())"/> 
            <xsl:variable name="os" select="HttpURLConnection:getOutputStream($conn)"/>
            <xsl:variable name="utf8Charset" select="Charset:forName('utf-8')"/>
            <xsl:variable name="payloadJavaString" as="jt:java.lang.String" select="JString:new($payload)"/>
            <xsl:variable name="byteArray" select="ByteBuffer:array(Charset:encode($utf8Charset, $payloadJavaString))"/>
            <xsl:message select="OutputStream:write($os, $byteArray, 0, count($byteArray))"/>
            <xsl:message select="OutputStream:close($os)"/>
            <xsl:variable name="responseCode" select="HttpURLConnection:getResponseCode($conn)"/>
            <xsl:variable name="br" select="BufferedReader:new(InputStreamReader:new(HttpURLConnection:getInputStream($conn), 'utf-8'))"/>
            <xsl:variable name="response" select="BufferedReader:lines($br) => Stream:toArray()"/>
            <result code="{$responseCode}">{$response}</result>
        </xsl:template>
        
    </xsl:stylesheet>