DEV Community

paohaijiao
paohaijiao

Posted on Originally published at paohaijiao.hashnode.dev

Conditional Requests at Runtime: Rendering Parts of a curl Command with XML `<if>`

HTTP requests are rarely static in production. A header should only be sent when a feature flag is on, verbose logging should appear only in debug sessions, and an endpoint variant should be selected by environment. JQuickCurl's XML mode solves this with conditional rendering: an <if test="..."> element whose enclosed curl text is included only when the expression evaluates to true — evaluated per request, at runtime.

This post shows:

  • How <if> composes with the XML catalog from Post 7.
  • Realistic conditions: headers, options, and body fragments.
  • A complete runnable demo with a toggled debug mode.

The Rule: Only What's True Gets Rendered

Inside a <curl> element you can wrap any curl fragment in <if test="expression">. If the expression holds, the fragment's text is spliced into the final command; otherwise it disappears as if never written.

<curl name="getUserByIdVariable" returnClass="com.example.model.User">
    curl -X GET #{host} \
    <if test="a == 1"> -H "Content-Type: application/json" </if>
</curl>
Enter fullscreen mode Exit fullscreen mode

The expression reads values from the request context — which includes the @Param-annotated arguments you pass to the interface method (Post 7). Here, the extra header appears only when parameter a equals 1.

Practical Pattern 1: Toggle Debug Verbosity

Wrap the curl logging flag so that production traffic is silent while a debug flag on the interface adds -v:

<curls namespace="com.example.search.SearchApi">

    <curl name="search" returnClass="java.lang.String">
        curl -X GET 'https://api.example.com/search?q=#{keyword}&page=#{page}'
        <if test="debug == true"> -v </if>
    </curl>

</curls>
Enter fullscreen mode Exit fullscreen mode
public interface SearchApi {
    String search(@Param("keyword") String keyword,
                  @Param("page") int page,
                  @Param("debug") boolean debug);
}
Enter fullscreen mode Exit fullscreen mode

Calling search("http", 1, true) adds -v to the command; calling it with false drops it. The same request shape, one conditional branch.

Practical Pattern 2: Conditional Header Injection

Environment-sensitive headers are a perfect <if> target. Inject a tracing header only when a traceId is actually present, and an API-key header only for the private environment:

<curl name="orders" returnClass="java.lang.String">
    curl -X GET '#{host}/orders?status=#{status}'
    <if test="traceId != null and traceId != ''"> -H "X-Trace-Id: #{traceId}" </if>
    <if test="env == 'prd'"> -H "X-Env: prd" -H "Authorization: Bearer #{token}" </if>
</curl>
Enter fullscreen mode Exit fullscreen mode
public interface OrderApi {
    String orders(@Param("host") String host,
                  @Param("status") String status,
                  @Param("traceId") String traceId,
                  @Param("env") String env,
                  @Param("token") String token);
}
Enter fullscreen mode Exit fullscreen mode

Same method, but the wire request differs with runtime state — no if/else in Java, no empty headers being sent.

Practical Pattern 3: Choose the Payload

Conditions can also select between body fragments. Sending a body only for methods that need one:

<curl name="report" returnClass="java.lang.String">
    curl -X POST 'https://api.example.com/reports'
    -H 'Content-Type: application/json'
    -d '{"type":"#{kind}"}'
    <if test="includeFilter == true"> ,"filter":{"min":#{min}} </if>
</curl>
Enter fullscreen mode Exit fullscreen mode

When splicing JSON fragments, keep the result valid JSON after substitution. Several small <if> blocks are easier to reason about than one opaque mega-expression.

Runnable Demo

Put the catalog below on the classpath as search.xml and run the Java class.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE curls PUBLIC "-//PAOHAIJIAO//DTD API CURL 1.0//EN"
        "classpath:paohaijiao/dtd/Jquick-curl.dtd">
<curls namespace="com.example.search.SearchApi">
    <curl name="search" returnClass="java.lang.String">
        curl -X GET 'https://httpbin.org/anything/search?q=#{q}'
        <if test="verbose == true"> -v </if>
        <if test="withHeader == true"> -H "X-Demo: conditional" </if>
    </curl>
</curls>
Enter fullscreen mode Exit fullscreen mode
import com.github.paohaijiao.xml.JQuickCurlXmlParseFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.param.Param;

public interface SearchApi {
    String search(@Param("q") String q,
                  @Param("verbose") boolean verbose,
                  @Param("withHeader") boolean withHeader);
}

class ConditionalDemo {
    public static void main(String[] args) {
        JQuickParseHandler parser = new JQuickCurlXmlParseFactory();
        SearchApi api = new JQuickXmlFactory(parser, "search.xml").createApi(SearchApi.class);

        // Verbose + header enabled
        System.out.println(api.search("curl", true, true));

        // Both disabled — the request on the wire is different
        System.out.println(api.search("curl", false, false));
    }
}
Enter fullscreen mode Exit fullscreen mode

Because the endpoint is httpbin.org/anything, the response echoes the headers and args the server actually received — you can visually confirm that X-Demo appears only in the first call.

Beyond <if>: The Full XML Control Set

The bundled DTD defines more than if. XML content may also use:

  • <foreach collection="..." item="..."> — repeat content over a collection with open, close, and separator.
  • <choose> / <when test="..."> / <otherwise> — multi-branch selection, similar to a switch.

Use them for the same goal: keeping the curl command truthful while letting runtime data shape it.

Summary

XML <if> turns a static curl catalog into a runtime decision graph. Headers, flags, and payload fragments are included only when their test expression is true, evaluated fresh on every invocation. The Java interface stays declarative and the request stays readable — the conditional logic lives where the request is defined, not sprinkled through callers.

Repository: dromara/jquick-curl. Post 9 covers the opposite end of HTTP payloads: binary and multipart file uploads through curl's -F flag.

java #springboot #httpclient #opensource #java-library

Top comments (0)