Lab 2 – Call HTTP & OpenAPI services
In this lab, you will extend your Quarkus Flow application to communicate with the outside world.
You will:
-
Create a dummy secured REST endpoint to act as a downstream service.
-
Add an HTTP task to your workflow to call that secured endpoint.
-
Call an OpenAPI operation simply by referencing its
operationId. -
Secure the calls using the Quarkus
CredentialsProviderfallback mechanism. -
See how Quarkus Flow automatically maps HTTP failures (
WorkflowException) into standard RFC 7807 Problem Details responses. -
Use the Dev UI and trace logs to troubleshoot network issues.
|
For your reference, this lab has a full working example here: https://github.com/quarkiverse/quarkus-flow/tree/main/examples/http-basic-auth |
1. Add HTTP client and Secret configuration
First, let’s configure the HTTP client timeouts, enable wire logging to see the actual payloads, and define a dummy secret in your application.properties for local testing.
(We will also define a quick test user for Quarkus Security so our dummy endpoint can authenticate the request).
# HTTP Client Tuning
quarkus.flow.http.client.connect-timeout=5000
quarkus.flow.http.client.read-timeout=10000
# Enable RESTEasy Reactive wire logging
quarkus.flow.http.client.logging.scope=request-response
quarkus.flow.http.client.logging.body-limit=2048
quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG
# Define a local dummy secret for the workflow to use
demo.username=alice
demo.password=s3cr3t!
# Define the Quarkus Security test user for the downstream endpoint to validate against
quarkus.security.users.embedded.users.alice=s3cr3t!
quarkus.security.users.embedded.roles.alice=user
(For advanced details on these settings, see Configure the HTTP client and Resolve secrets securely).
2. Create the secured downstream service
To test our workflow, we need a downstream service that requires authentication. Create a standard JAX-RS resource protected by @RolesAllowed("user"):
package org.acme.example;
import java.time.Instant;
import java.util.Map;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
@Path("/secure/profile")
@Produces(MediaType.APPLICATION_JSON)
public class SecureCustomerProfileResource {
@Context
SecurityContext securityContext;
@GET
@RolesAllowed("user")
public Map<String, Object> getProfile() {
// In a real system, you'd look up the current customer context.
// Here we just return a canned "profile" plus the caller principal.
String caller = securityContext.getUserPrincipal() != null ? securityContext.getUserPrincipal().getName()
: "anonymous";
return Map.of("customerId", 123, "name", "Jane Doe", "tier", "GOLD", "lastUpdatedAt", Instant.now().toString(),
"servedBy", caller);
}
}
3. Add the HTTP workflow
Now, create the workflow that calls your newly secured endpoint.
package org.acme.example;
import static io.serverlessworkflow.fluent.func.FuncWorkflowBuilder.workflow;
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.get;
import static io.serverlessworkflow.fluent.spec.dsl.DSL.basic;
import static io.serverlessworkflow.fluent.spec.dsl.DSL.secret;
import java.net.URI;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import io.quarkiverse.flow.Flow;
import io.serverlessworkflow.api.types.Workflow;
@ApplicationScoped
public class CustomerProfileFlow extends Flow {
@ConfigProperty(name = "demo.server", defaultValue = "http://localhost:8080")
String securedServer;
@Override
public Workflow descriptor() {
URI endpoint = URI.create(securedServer + "/secure/profile");
return workflow("secure-customer-profile")
// Load secrets into workflow data (see secrets documentation)
.use(secret("demo"))
.tasks(
// GET with HTTP Basic credentials taken from the secret
get(endpoint, basic("${ $secret.demo.username }", "${ $secret.demo.password }")))
.build();
}
}
Notice how this workflow manages security:
-
.use(secret("demo"))tells the engine to load thedemosecret bundle. -
get(endpoint, basic(…))attaches the HTTP Basic Auth headers by injecting thedemo.usernameanddemo.passworddirectly from the workflow’s secure context.
4. Expose the workflow as a REST endpoint
Create a JAX-RS resource that invokes the workflow so you can trigger it from your browser:
package org.acme.example;
import java.util.Map;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import io.smallrye.mutiny.Uni;
@Path("/api/profile")
@Produces(MediaType.APPLICATION_JSON)
public class CustomerProfileResource {
@Inject
CustomerProfileFlow customerProfileFlow;
@GET
public Uni<Map<String, Object>> getProfileViaWorkflow() {
return customerProfileFlow.startInstance().onItem().transform(r -> r.asMap().orElseThrow());
}
}
Key points:
-
The endpoint is non-blocking, returning a
Uni<Map<String, Object>>. -
Because it is reactive, if the HTTP call inside the workflow fails (e.g., a
401 Unauthorizedor404 Not Found), Quarkus Flow’sExceptionMappercatches it automatically.
5. Try a failing call
With dev mode running (./mvnw quarkus:dev):
-
Call the endpoint but intentionally break the credentials (for example, change
demo.password=wrongin yourapplication.properties). -
Observe the HTTP response. You won’t get a messy Java stack trace; you get a clean, standard Problem Details response:
HTTP/1.1 401 Unauthorized Content-Type: application/json { "type": "https://serverlessworkflow.io/spec/1.0.0/errors/communication", "status": 401, "title": "HTTP 401 Unauthorized", "instance": "do/0/..." } -
Open the Quarkus Dev UI (
http://localhost:8080/q/dev). Check the logs from the REST client logger to see the exact wire request that failed!
6. Call an OpenAPI operation
If you have an OpenAPI specification file for a downstream service, you don’t even need to write the HTTP URL or method.
-
Download the Petstore Swagger JSON and place it in your project at
src/main/resources/openapi/petstore.json. -
Add a new workflow that uses the
openapi()builder:
package org.acme.example;
import static io.serverlessworkflow.fluent.func.FuncWorkflowBuilder.workflow;
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.openapi;
import java.net.URI;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkiverse.flow.Flow;
import io.serverlessworkflow.api.types.Workflow;
@ApplicationScoped
public class PetstoreFlow extends Flow {
@Override
public Workflow descriptor() {
final URI petstoreUri = URI.create("openapi/petstore.json");
return workflow("petstore")
.tasks(
// 1) Find pets by status
openapi()
.document(petstoreUri).operation("findPetsByStatus").parameter("status", "sold")
// Pick the first pet id and expose it as `selectedPetId`
.outputAs("${ { selectedPetId: .[0].id } }"),
// 2) Fetch that pet by id
openapi()
.document(petstoreUri).operation("getPetById").parameter("petId", "${ .selectedPetId }"))
.build();
}
}
Run the workflow and inspect the Dev UI traces. You will see that Quarkus Flow automatically read the petstore.json file, found the findPetsByStatus operation, determined it was a GET request, and appended the parameters correctly to the URL.
7. Summary
You have successfully completed Lab 2! You learned:
-
How to execute standard HTTP and OpenAPI calls from your workflows.
-
How to use local
application.propertiesto mock secrets for HTTP Authentication. -
How HTTP failures seamlessly translate into
WorkflowException/WorkflowErrorRFC 7807 responses. -
How wire logging makes troubleshooting third-party APIs incredibly easy.
Next up: Let’s dive into asynchronous, event-driven orchestrations. Proceed to Lab 3 – Events with YAML workflows.