mirror of
https://github.com/alexandrev/xslt-lab.git
synced 2026-09-14 09:03:15 +00:00
Add per-version XSLT engines: XSLTC (1.0), Saxon 9 (2.0), Saxon 12 (3.0)
Each XSLT version now runs against its own true processor rather than everything going through Saxon 12 in backwards-compatible mode: - version=1.0 → port 8082, JDK built-in XSLTC (proper XSLT 1.0 validator) - version=2.0 → port 8083, Saxon HE 9.9.1.8 (true XSLT 2.0 semantics) - version=3.0 → port 8081, Saxon HE 12.5 (unchanged) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+63
-27
@@ -1,57 +1,93 @@
|
||||
# Etapa de build
|
||||
# Build stage
|
||||
FROM golang:1.23-alpine AS builder
|
||||
|
||||
# Instalar dependencias necesarias
|
||||
RUN apk add --no-cache git openjdk17 curl
|
||||
|
||||
# Establecer directorio de trabajo
|
||||
# ── Go binary ────────────────────────────────────────────────────────────────
|
||||
WORKDIR /app/src
|
||||
|
||||
# Copiar los archivos
|
||||
COPY src/ .
|
||||
RUN go mod tidy && go build -o server .
|
||||
|
||||
# Compilar el binario estático
|
||||
RUN go mod tidy && \
|
||||
go build -o server .
|
||||
|
||||
# Build extension functions jar + SaxonDaemon
|
||||
# ── Java daemons ─────────────────────────────────────────────────────────────
|
||||
WORKDIR /app/ext
|
||||
COPY ext/ .
|
||||
RUN mkdir -p /tmp/saxon && \
|
||||
curl -L -o /tmp/saxon/saxon-he.jar https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/12.5/Saxon-HE-12.5.jar && \
|
||||
curl -L -o /tmp/saxon/gson.jar https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar && \
|
||||
javac -cp "/tmp/saxon/saxon-he.jar:/tmp/saxon/gson.jar" \
|
||||
|
||||
# Download JARs for Saxon 12 (XSLT 3.0)
|
||||
RUN mkdir -p /tmp/saxon12 && \
|
||||
curl -L -o /tmp/saxon12/saxon-he.jar \
|
||||
https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/12.5/Saxon-HE-12.5.jar && \
|
||||
curl -L -o /tmp/saxon12/gson.jar \
|
||||
https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar
|
||||
|
||||
# Download JARs for Saxon 9 (XSLT 2.0)
|
||||
RUN mkdir -p /tmp/saxon9 && \
|
||||
curl -L -o /tmp/saxon9/saxon-he.jar \
|
||||
https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/9.9.1.8/Saxon-HE-9.9.1.8.jar && \
|
||||
curl -L -o /tmp/saxon9/gson.jar \
|
||||
https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar
|
||||
|
||||
# Compile SaxonDaemon + Runner + CustomFunctions against Saxon 12
|
||||
RUN mkdir -p /tmp/classes12 && \
|
||||
javac -cp "/tmp/saxon12/saxon-he.jar:/tmp/saxon12/gson.jar" \
|
||||
-d /tmp/classes12 \
|
||||
com/xsltplayground/ext/CustomFunctions.java \
|
||||
com/xsltplayground/Runner.java \
|
||||
com/xsltplayground/SaxonDaemon.java && \
|
||||
jar cf custom-functions.jar com
|
||||
jar cf /tmp/custom-functions-12.jar -C /tmp/classes12 .
|
||||
|
||||
# Compile Saxon2Daemon + Runner + CustomFunctions against Saxon 9
|
||||
RUN mkdir -p /tmp/classes9 && \
|
||||
javac -cp "/tmp/saxon9/saxon-he.jar:/tmp/saxon9/gson.jar" \
|
||||
-d /tmp/classes9 \
|
||||
com/xsltplayground/ext/CustomFunctions.java \
|
||||
com/xsltplayground/Runner.java \
|
||||
com/xsltplayground/Saxon2Daemon.java && \
|
||||
jar cf /tmp/custom-functions-9.jar -C /tmp/classes9 .
|
||||
|
||||
# Compile XalanDaemon — JDK built-in XSLT 1.0 processor, only gson needed
|
||||
RUN mkdir -p /tmp/classesxalan && \
|
||||
javac -cp "/tmp/saxon12/gson.jar" \
|
||||
-d /tmp/classesxalan \
|
||||
com/xsltplayground/XalanDaemon.java && \
|
||||
jar cf /tmp/custom-functions-xalan.jar -C /tmp/classesxalan .
|
||||
|
||||
WORKDIR /app/src
|
||||
|
||||
# Etapa final (runtime)
|
||||
# Runtime stage
|
||||
FROM alpine:latest
|
||||
|
||||
# Instalar Java y wget (para el health-check en start.sh)
|
||||
RUN apk add --no-cache openjdk17-jre curl wget
|
||||
|
||||
# Crear directorio de trabajo
|
||||
WORKDIR /app
|
||||
|
||||
# Copiar binario, configuración y script de arranque
|
||||
COPY --from=builder /app/src/server .
|
||||
COPY app.config .
|
||||
COPY start.sh .
|
||||
RUN chmod +x start.sh
|
||||
|
||||
# Crear carpeta para Saxon y descargar JARs
|
||||
RUN mkdir -p /opt/saxon && \
|
||||
curl -L -o /opt/saxon/saxon-he.jar https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/12.5/Saxon-HE-12.5.jar && \
|
||||
curl -L -o /opt/saxon/xmlresolver.jar https://repo1.maven.org/maven2/org/xmlresolver/xmlresolver/4.5.0/xmlresolver-4.5.0.jar && \
|
||||
curl -L -o /opt/saxon/gson.jar https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar
|
||||
# Saxon 12 (XSLT 3.0) — port 8081
|
||||
RUN mkdir -p /opt/saxon12 && \
|
||||
curl -L -o /opt/saxon12/saxon-he.jar \
|
||||
https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/12.5/Saxon-HE-12.5.jar && \
|
||||
curl -L -o /opt/saxon12/xmlresolver.jar \
|
||||
https://repo1.maven.org/maven2/org/xmlresolver/xmlresolver/4.5.0/xmlresolver-4.5.0.jar && \
|
||||
curl -L -o /opt/saxon12/gson.jar \
|
||||
https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar
|
||||
COPY --from=builder /tmp/custom-functions-12.jar /opt/saxon12/
|
||||
|
||||
# Extension functions + daemon jar
|
||||
COPY --from=builder /app/ext/custom-functions.jar /opt/saxon/
|
||||
# Saxon 9 (XSLT 2.0) — port 8083
|
||||
RUN mkdir -p /opt/saxon9 && \
|
||||
curl -L -o /opt/saxon9/saxon-he.jar \
|
||||
https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/9.9.1.8/Saxon-HE-9.9.1.8.jar && \
|
||||
curl -L -o /opt/saxon9/gson.jar \
|
||||
https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar
|
||||
COPY --from=builder /tmp/custom-functions-9.jar /opt/saxon9/
|
||||
|
||||
# XSLTC / JDK built-in (XSLT 1.0) — port 8082
|
||||
RUN mkdir -p /opt/xalan && \
|
||||
curl -L -o /opt/xalan/gson.jar \
|
||||
https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar
|
||||
COPY --from=builder /tmp/custom-functions-xalan.jar /opt/xalan/
|
||||
|
||||
# Exponer el puerto del API y arrancar con el script
|
||||
EXPOSE 8000
|
||||
CMD ["./start.sh"]
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package com.xsltplayground;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.sun.net.httpserver.*;
|
||||
import com.xsltplayground.ext.CustomFunctions;
|
||||
import net.sf.saxon.lib.FeatureKeys;
|
||||
import net.sf.saxon.s9api.*;
|
||||
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import java.io.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* XSLT 2.0 daemon using Saxon HE 9.x.
|
||||
* Runs on port 8083. Compiled and run against Saxon 9 JARs.
|
||||
*/
|
||||
public class Saxon2Daemon {
|
||||
|
||||
static final Processor PROCESSOR;
|
||||
static final Processor TRACE_PROCESSOR;
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
static {
|
||||
PROCESSOR = new Processor(false);
|
||||
CustomFunctions.registerAll(PROCESSOR);
|
||||
|
||||
TRACE_PROCESSOR = new Processor(false);
|
||||
TRACE_PROCESSOR.setConfigurationProperty(FeatureKeys.OPTIMIZATION_LEVEL, "0");
|
||||
CustomFunctions.registerAll(TRACE_PROCESSOR);
|
||||
|
||||
String warmupXslt =
|
||||
"<xsl:stylesheet version='2.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" +
|
||||
"<xsl:template match='/'><out/></xsl:template></xsl:stylesheet>";
|
||||
String warmupXml = "<root/>";
|
||||
for (Processor proc : new Processor[]{PROCESSOR, TRACE_PROCESSOR}) {
|
||||
try {
|
||||
XsltCompiler c = proc.newXsltCompiler();
|
||||
XsltExecutable exec = c.compile(new StreamSource(new StringReader(warmupXslt)));
|
||||
XsltTransformer t = exec.load();
|
||||
XdmNode doc = proc.newDocumentBuilder()
|
||||
.build(new StreamSource(new StringReader(warmupXml)));
|
||||
t.setInitialContextNode(doc);
|
||||
Serializer ser = proc.newSerializer(new StringWriter());
|
||||
t.setDestination(ser);
|
||||
t.transform();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Saxon2Daemon warm-up warning: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
System.out.println("Saxon2Daemon: warm-up complete.");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int port = 8083;
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", port), 32);
|
||||
server.createContext("/transform", new TransformHandler());
|
||||
server.createContext("/health", exchange -> {
|
||||
byte[] resp = "ok".getBytes(StandardCharsets.UTF_8);
|
||||
exchange.getResponseHeaders().set("Content-Type", "text/plain");
|
||||
exchange.sendResponseHeaders(200, resp.length);
|
||||
try (OutputStream os = exchange.getResponseBody()) { os.write(resp); }
|
||||
});
|
||||
int threads = Math.max(2, Runtime.getRuntime().availableProcessors());
|
||||
server.setExecutor(Executors.newFixedThreadPool(threads));
|
||||
server.start();
|
||||
System.out.println("Saxon2Daemon ready on :" + port + " (threads=" + threads + ")");
|
||||
}
|
||||
|
||||
static class TransformHandler implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
if (!exchange.getRequestMethod().equalsIgnoreCase("POST")) {
|
||||
exchange.sendResponseHeaders(405, -1);
|
||||
exchange.close();
|
||||
return;
|
||||
}
|
||||
|
||||
String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
|
||||
JsonObject response = new JsonObject();
|
||||
int status = 200;
|
||||
|
||||
try {
|
||||
JsonObject req = GSON.fromJson(body, JsonObject.class);
|
||||
|
||||
String xslt = req.has("xslt") ? req.get("xslt").getAsString() : "";
|
||||
String source = req.has("source") ? req.get("source").getAsString() : "";
|
||||
boolean trace = req.has("trace") && req.get("trace").getAsBoolean();
|
||||
|
||||
Map<String, String> params = jsonObjectToMap(req, "parameters");
|
||||
Map<String, String> fileParams = jsonObjectToMap(req, "fileParameters");
|
||||
|
||||
Processor proc = trace ? TRACE_PROCESSOR : PROCESSOR;
|
||||
|
||||
ByteArrayOutputStream traceBuf = new ByteArrayOutputStream();
|
||||
PrintStream traceSink = new PrintStream(traceBuf, true, StandardCharsets.UTF_8);
|
||||
|
||||
XsltCompiler compiler = proc.newXsltCompiler();
|
||||
compiler.setErrorReporter(
|
||||
new Runner.DeduplicatingErrorReporter(compiler.getErrorReporter()));
|
||||
|
||||
boolean instrumentationEnabled = false;
|
||||
if (trace) {
|
||||
instrumentationEnabled = Runner.enableCompileWithTracing(compiler);
|
||||
}
|
||||
|
||||
XsltExecutable exec;
|
||||
try {
|
||||
exec = compiler.compile(new StreamSource(new StringReader(xslt)));
|
||||
} catch (SaxonApiException e) {
|
||||
if (trace && instrumentationEnabled) {
|
||||
compiler = proc.newXsltCompiler();
|
||||
compiler.setErrorReporter(
|
||||
new Runner.DeduplicatingErrorReporter(compiler.getErrorReporter()));
|
||||
exec = compiler.compile(new StreamSource(new StringReader(xslt)));
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
XsltTransformer transformer = exec.load();
|
||||
|
||||
if (trace) {
|
||||
Runner.attachTraceListener(proc, transformer, traceSink);
|
||||
}
|
||||
|
||||
if (source != null && !source.isEmpty()) {
|
||||
XdmNode doc = proc.newDocumentBuilder()
|
||||
.build(new StreamSource(new StringReader(source)));
|
||||
transformer.setInitialContextNode(doc);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> e : params.entrySet()) {
|
||||
transformer.setParameter(new QName(e.getKey()), new XdmAtomicValue(e.getValue()));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> e : fileParams.entrySet()) {
|
||||
String val = e.getValue().trim();
|
||||
if (val.startsWith("<")) {
|
||||
XdmNode node = proc.newDocumentBuilder()
|
||||
.build(new StreamSource(new StringReader(val)));
|
||||
transformer.setParameter(new QName(e.getKey()), node);
|
||||
} else {
|
||||
transformer.setParameter(new QName(e.getKey()), new XdmAtomicValue(val));
|
||||
}
|
||||
}
|
||||
|
||||
StringWriter resultWriter = new StringWriter();
|
||||
Serializer ser = proc.newSerializer(resultWriter);
|
||||
transformer.setDestination(ser);
|
||||
transformer.transform();
|
||||
|
||||
traceSink.flush();
|
||||
response.addProperty("result", resultWriter.toString());
|
||||
response.addProperty("traceText", trace ? traceBuf.toString(StandardCharsets.UTF_8) : "");
|
||||
|
||||
} catch (SaxonApiException e) {
|
||||
response.addProperty("error", e.getMessage() != null ? e.getMessage() : e.toString());
|
||||
status = 400;
|
||||
} catch (Exception e) {
|
||||
response.addProperty("error", e.toString());
|
||||
status = 500;
|
||||
}
|
||||
|
||||
byte[] respBytes = GSON.toJson(response).getBytes(StandardCharsets.UTF_8);
|
||||
exchange.getResponseHeaders().set("Content-Type", "application/json");
|
||||
exchange.sendResponseHeaders(status, respBytes.length);
|
||||
try (OutputStream os = exchange.getResponseBody()) { os.write(respBytes); }
|
||||
}
|
||||
|
||||
private Map<String, String> jsonObjectToMap(JsonObject req, String key) {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
if (req.has(key) && req.get(key).isJsonObject()) {
|
||||
for (Map.Entry<String, JsonElement> e : req.getAsJsonObject(key).entrySet()) {
|
||||
result.put(e.getKey(), e.getValue().getAsString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.xsltplayground;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.sun.net.httpserver.*;
|
||||
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.transform.stream.*;
|
||||
import java.io.*;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* XSLT 1.0 daemon using the JDK's built-in XSLT processor (XSLTC / Xalan-J).
|
||||
* Runs on port 8082. No Saxon dependency.
|
||||
*/
|
||||
public class XalanDaemon {
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
private static final int PORT = 8082;
|
||||
|
||||
static {
|
||||
// Force JDK built-in XSLTC (not Saxon, which might be on the classpath)
|
||||
System.setProperty("javax.xml.transform.TransformerFactory",
|
||||
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
|
||||
|
||||
// Warm up
|
||||
try {
|
||||
TransformerFactory.newInstance().newTemplates(new StreamSource(new StringReader(
|
||||
"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" +
|
||||
"<xsl:template match='/'><out/></xsl:template></xsl:stylesheet>")));
|
||||
} catch (Exception e) {
|
||||
System.err.println("XalanDaemon warm-up warning: " + e.getMessage());
|
||||
}
|
||||
System.out.println("XalanDaemon: warm-up complete.");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", PORT), 32);
|
||||
server.createContext("/transform", new TransformHandler());
|
||||
server.createContext("/health", exchange -> {
|
||||
byte[] resp = "ok".getBytes(StandardCharsets.UTF_8);
|
||||
exchange.getResponseHeaders().set("Content-Type", "text/plain");
|
||||
exchange.sendResponseHeaders(200, resp.length);
|
||||
try (OutputStream os = exchange.getResponseBody()) { os.write(resp); }
|
||||
});
|
||||
int threads = Math.max(2, Runtime.getRuntime().availableProcessors());
|
||||
server.setExecutor(Executors.newFixedThreadPool(threads));
|
||||
server.start();
|
||||
System.out.println("XalanDaemon ready on :" + PORT + " (threads=" + threads + ")");
|
||||
}
|
||||
|
||||
static class TransformHandler implements HttpHandler {
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
if (!exchange.getRequestMethod().equalsIgnoreCase("POST")) {
|
||||
exchange.sendResponseHeaders(405, -1);
|
||||
exchange.close();
|
||||
return;
|
||||
}
|
||||
|
||||
String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
|
||||
JsonObject response = new JsonObject();
|
||||
int status = 200;
|
||||
|
||||
try {
|
||||
JsonObject req = GSON.fromJson(body, JsonObject.class);
|
||||
|
||||
String xslt = req.has("xslt") ? req.get("xslt").getAsString() : "";
|
||||
String source = req.has("source") ? req.get("source").getAsString() : "";
|
||||
|
||||
Map<String, String> params = jsonObjectToMap(req, "parameters");
|
||||
Map<String, String> fileParams = jsonObjectToMap(req, "fileParameters");
|
||||
|
||||
// Collect warnings
|
||||
StringBuilder warnings = new StringBuilder();
|
||||
ErrorListener errorListener = new ErrorListener() {
|
||||
@Override public void warning(TransformerException e) {
|
||||
warnings.append("Warning: ").append(e.getMessage()).append("\n");
|
||||
}
|
||||
@Override public void error(TransformerException e) throws TransformerException { throw e; }
|
||||
@Override public void fatalError(TransformerException e) throws TransformerException { throw e; }
|
||||
};
|
||||
|
||||
TransformerFactory factory = TransformerFactory.newInstance();
|
||||
factory.setErrorListener(errorListener);
|
||||
Templates templates = factory.newTemplates(new StreamSource(new StringReader(xslt)));
|
||||
Transformer transformer = templates.newTransformer();
|
||||
transformer.setErrorListener(errorListener);
|
||||
|
||||
for (Map.Entry<String, String> e : params.entrySet()) {
|
||||
transformer.setParameter(e.getKey(), e.getValue());
|
||||
}
|
||||
// File params: XSLT 1.0 doesn't support node-typed params natively — pass as string
|
||||
for (Map.Entry<String, String> e : fileParams.entrySet()) {
|
||||
transformer.setParameter(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
// XSLT 1.0 always requires a source document (no xsl:initial-template)
|
||||
String src = (source != null && !source.isEmpty()) ? source : "<root/>";
|
||||
StringWriter resultWriter = new StringWriter();
|
||||
transformer.transform(new StreamSource(new StringReader(src)), new StreamResult(resultWriter));
|
||||
|
||||
response.addProperty("result", resultWriter.toString());
|
||||
response.addProperty("traceText", warnings.toString());
|
||||
|
||||
} catch (TransformerException e) {
|
||||
response.addProperty("error", formatError(e));
|
||||
status = 400;
|
||||
} catch (Exception e) {
|
||||
response.addProperty("error", e.getMessage() != null ? e.getMessage() : e.toString());
|
||||
status = 500;
|
||||
}
|
||||
|
||||
byte[] respBytes = GSON.toJson(response).getBytes(StandardCharsets.UTF_8);
|
||||
exchange.getResponseHeaders().set("Content-Type", "application/json");
|
||||
exchange.sendResponseHeaders(status, respBytes.length);
|
||||
try (OutputStream os = exchange.getResponseBody()) { os.write(respBytes); }
|
||||
}
|
||||
|
||||
private static String formatError(TransformerException e) {
|
||||
SourceLocator loc = e.getLocator();
|
||||
String msg = e.getMessage() != null ? e.getMessage() : e.toString();
|
||||
if (loc != null && loc.getLineNumber() > 0) {
|
||||
return "Error at line " + loc.getLineNumber() +
|
||||
", column " + loc.getColumnNumber() + ": " + msg;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
private Map<String, String> jsonObjectToMap(JsonObject req, String key) {
|
||||
Map<String, String> result = new LinkedHashMap<>();
|
||||
if (req.has(key) && req.get(key).isJsonObject()) {
|
||||
for (Map.Entry<String, JsonElement> e : req.getAsJsonObject(key).entrySet()) {
|
||||
result.put(e.getKey(), e.getValue().getAsString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -218,10 +218,18 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
daemonPort := "8081" // Saxon 12 — XSLT 3.0 (default)
|
||||
switch req.Version {
|
||||
case "1.0":
|
||||
daemonPort = "8082" // XSLTC (JDK) — true XSLT 1.0
|
||||
case "2.0":
|
||||
daemonPort = "8083" // Saxon 9 — true XSLT 2.0
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
httpClient := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := httpClient.Post(
|
||||
"http://127.0.0.1:8081/transform",
|
||||
"http://127.0.0.1:"+daemonPort+"/transform",
|
||||
"application/json",
|
||||
bytes.NewReader(daemonBody),
|
||||
)
|
||||
@@ -250,7 +258,7 @@ func main() {
|
||||
}
|
||||
|
||||
if daemonResp.Error != "" {
|
||||
log.Printf("saxon error after %dms: %s", duration, daemonResp.Error)
|
||||
log.Printf("transform error after %dms: %s", duration, daemonResp.Error)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": daemonResp.Error})
|
||||
return
|
||||
}
|
||||
|
||||
+36
-16
@@ -1,27 +1,47 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Start Saxon daemon in background
|
||||
# ── Saxon 12 — XSLT 3.0 (port 8081) ─────────────────────────────────────────
|
||||
java \
|
||||
-Xms64m -Xmx256m \
|
||||
-XX:+UseSerialGC \
|
||||
-cp '/opt/saxon/*' \
|
||||
-cp '/opt/saxon12/*' \
|
||||
com.xsltplayground.SaxonDaemon &
|
||||
|
||||
DAEMON_PID=$!
|
||||
# ── XSLTC / JDK — XSLT 1.0 (port 8082) ──────────────────────────────────────
|
||||
java \
|
||||
-Xms32m -Xmx128m \
|
||||
-XX:+UseSerialGC \
|
||||
-cp '/opt/xalan/*' \
|
||||
com.xsltplayground.XalanDaemon &
|
||||
|
||||
# Wait until the daemon responds on /health (max 30s)
|
||||
echo "Waiting for SaxonDaemon..."
|
||||
TRIES=0
|
||||
until wget -qO- http://127.0.0.1:8081/health > /dev/null 2>&1; do
|
||||
TRIES=$((TRIES + 1))
|
||||
if [ $TRIES -ge 60 ]; then
|
||||
echo "SaxonDaemon did not start in time" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
echo "SaxonDaemon ready after ${TRIES} probes."
|
||||
# ── Saxon 9 — XSLT 2.0 (port 8083) ──────────────────────────────────────────
|
||||
java \
|
||||
-Xms32m -Xmx128m \
|
||||
-XX:+UseSerialGC \
|
||||
-cp '/opt/saxon9/*' \
|
||||
com.xsltplayground.Saxon2Daemon &
|
||||
|
||||
# Start Go server in foreground
|
||||
# ── Wait for all three daemons ────────────────────────────────────────────────
|
||||
wait_for() {
|
||||
PORT=$1
|
||||
NAME=$2
|
||||
echo "Waiting for $NAME on :$PORT..."
|
||||
TRIES=0
|
||||
until wget -qO- "http://127.0.0.1:$PORT/health" > /dev/null 2>&1; do
|
||||
TRIES=$((TRIES + 1))
|
||||
if [ $TRIES -ge 60 ]; then
|
||||
echo "$NAME did not start in time" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
echo "$NAME ready after ${TRIES} probes."
|
||||
}
|
||||
|
||||
wait_for 8081 SaxonDaemon
|
||||
wait_for 8082 XalanDaemon
|
||||
wait_for 8083 Saxon2Daemon
|
||||
|
||||
# ── Go server in foreground ───────────────────────────────────────────────────
|
||||
exec ./server
|
||||
|
||||
Reference in New Issue
Block a user