If you still have some legacy SOAP (Simple Object Access Protocol) applications, it is possible to keep running them while modernizing your connectivity, giving you a more robust operating environment so that you can upgrade or replace them when you are ready. The purpose of this post is to show by example how you can now publish these SOAP interfaces using Gloo Edge Enterprise (an enhanced Envoy Proxy API gateway) with Gloo Portal 1.1 (a developer’s portal to share APIs).
Looking for information about SOAP interfaces may lead you to Google “soap introduction date,” in which case you may find an article identifying the Babylonians as the producers of the first soap around 2800 BCE. At first glance, that sounds about right since SOAP has seemingly been around for centuries now. Alas, the SOAP messaging protocols—and therefore SOAP interfaces—are of more recent origin. My own archeological research dates it back to about 2000 CE (which is roughly equivalent to 2800 BCE in computer years).
So those of us who are old enough have had plenty of time to build and deploy systems with SOAP interfaces, and you can still find them scattered across the enterprise computing landscape. Even though most organizations are not sponsoring new development using SOAP, there are scores of legacy SOAP assets still delivering value. The lives of these assets can be extended by fronting them with API gateway technology that updates their interfaces and security postures using modern standards.
Solo.io delivered on this promise with an XSLT 3.0 (Extensible Stylesheet Language Transformations) capability in the Gloo Edge Enterprise 1.8 release in June of this year. You can read more about that here and here.
Now with the Gloo Portal 1.1 release, you can not only access these systems using Gloo Edge, but also you can better manage the lifecycles of these APIs and even present them to consumers in a dynamically generated web portal.
All Kubernetes resources used here are available on GitHub.
Building an environment for modernizing or connecting your SOAP interfaces
In order to do this, you’ll need a Kubernetes cluster and associated tools, plus an instance of Gloo Edge Enterprise to complete this guide. In particular, we’ll be using the CLI utilities kubectl, glooctl, curl, and jq.
We used Google Kubernetes Engine (GKE) with Kubernetes v1.20.9 to test this guide, although any recent version with any Kubernetes provider should suffice. If you prefer to work locally instead of with a remote cluster, we’ve have good success with Kind. If you go the Kind route, use these instructions for your installation of Gloo Edge.
We use Gloo Edge Enterprise v1.9.0 as our API gateway. Use this guide if you need to install Gloo Edge Enterprise. If you don’t already have access to the enterprise bits of Gloo Edge, you can request a free trial here.
Gloo Portal allows you to catalog, manage and securely publish running APIs to onboard developers both inside and outside your organization. It supports both REST/OpenAPI and gRPC interfaces. In this post, we’ll show you how to publish SOAP/XML interfaces as well. And as with all Gloo products, these facilities are delivered with a cloud-native architecture that fits hand-in-glove with your Kubernetes and Istio deployments.
Gloo Portal is included with every Gloo Edge Enterprise subscription. But since not all customers use it, it is packaged as a separate installation from the core Edge API Gateway. We are using Portal v1.1.1 for this exercise. Follow this guide to install Portal with Gloo Edge as its deployment target.
Note that Gloo Portal also supports deployment to Gloo Mesh Gateway in addition to Gloo Edge. However, in this blog post we’ll focus strictly on deployment to Gloo Edge.
We’ll begin by establishing the base application and testing it out with its native SOAP/XML interface. Later we’ll progress to wrapping it with a REST/JSON facade and publishing its APIs with Gloo Portal.
Establishing the SOAP interface
In this exercise, we’ll use the World Cities example application as the basis for our portal work. It’s a simple application that provides a single query endpoint to perform a fuzzy search for city names in a public database. It is also the basis for the Gloo Edge SOAP/XML documentation.
The World Cities application is already built and packaged in an OCI (Open Container Initiative) container. We’ll use a standard Kubernetes service deployment configuration to spin it up in our cluster.
You should see this response:
Let’s confirm that the resulting pod spun up as expected:
We expect to see a running world-cities pod, like this:
Gloo Edge automatically discovers this service deployment and generates a Kubernetes custom resource called an Upstream
, to which the gateway can route requests. Let’s confirm that this Upstream
was created successfully using the expected naming convention. Note that for this operation, we’re using the glooctl
CLI utility, which you installed along with Gloo Edge.
Note that glooctl
shows both our Upstream
and the fact that its status is Accepted
.
Establishing an initial VirtualService
VirtualServices
are a key concept in Gloo Edge that define routing rules for requests coming into your cluster. They determine the ultimate Upstream
destination for the request, in addition to applying policies to manage security, rate limiting, transformations, and the like.
We will initially establish a VirtualService
that performs no SOAP/XML conversions. In other words, we’ll interact with the SOAP service in its native language.
The VirtualService
to accomplish this is quite simple, as you can see from its YAML configuration below. It accepts traffic from any domain, with any path prefix, and routes it to the Upstream
discovered for the world-cities service that we installed earlier.
Let’s apply the above configuration to create the VirtualService
.
You should see this response:
You can easily confirm that the VirtualService
was accepted using the glooctl
utility.
Expected response:
Configuring DNS rules
If you used the single kubectl
command above to create the portal artifacts, then you likely do not have the necessary network configuration in place. In order to visit the portal being served at our domain petstore.example.com
, we’ll need to make sure a DNS rule exists that will resolve that domain to that address.
Throughout this exercise, we are using hostnames to access the services we are publishing using Gloo Edge and Portal. Depending on your environment, you may need to adjust your network configuration if you want to follow along precisely. There are many ways to set up DNS rules for the domains defined in the VirtualServices
, Environments
and Portals
that we are creating.
We are using GKE to test this exercise, and GKE binds an external IP address to the Envoy proxy deployed by Gloo Edge. You can locate that address using the glooctl
utility like this:
Note that if you’re using a different platform like Amazon EKS, it may produce a hostname instead of an address. In that case, you’ll need to use a different approach like supplying a Host
header with your curl
commands. This example from the Gloo Portal docs might help.
But for the purposes of our setup with GKE using a simple workstation client, we’ll modify our local /etc/hosts
file with an entry to manually resolve the Environment
and Portal
domains that we’ll be using.
Let’s add entries for the worldcities.example.com
and api.worldcities.example.com
hostname, substituting INGRESS_HOST
with your IP address, as shown below.
Testing the SOAP application endpoint
With a Gloo Edge VirtualService
in place, we can now exercise the SOAP/XML endpoint before converting it to use a more modern REST/JSON interface.
Because we are not transforming the response, we should see a proper XML response to our SOAP/XML query:
Producing an OpenAPI interface
Gloo Portal supports API management using two popular interface standards today, OpenAPI and gRPC. (Stay tuned for GraphQL, which will be coming soon.) For this exercise, we will convert our SOAP/XML interface to REST/JSON by employing OpenAPI.
You can produce OpenAPI specifications from scratch, but it is often easier to use Swagger tools to get started. That’s what we did in this case, using free Swagger tools to create an initial OpenAPI specification based on sample calls to our interface as described here. We then tweaked it by hand a bit to produce this JSON specification document.
There are a couple of interesting interface changes represented in this document:
- The
SOAPAction
will still be passed in via request header; - The
cityQuery
string will be passed in a JSON-formatted request body; and - The response will be returned in JSON format as well.
Using XSLT to convert XML to JSON
Before we apply the new OpenAPI interface specification and build a portal, let’s first take a look at the XSLT transformations that will convert the XML input and output of our service into JSON. This is also covered in the Gloo Edge transformation docs.
There are three key elements of the request transformation:
xslt:
This is the payload transformation. It converts a simple request body like{"cityQuery": "south bo"}
into the full XML envelope required by the SOAP service, which we used in an earlier section. Note that a lot of the difficult work is delegated to the XSLT functionjson-to-xml
. We use it in the request XSLT to transform the core of the JSON input to XML, and vice versa later with the response.nonXmlTransform
: This is set totrue
since we are transforming JSON to XML. Natively, XSLT can only transform XML data. However, our input to the transformation is JSON, so by specifying this flag, we signal to our XSLT transformation filter that we are supplying non-XML (JSON) data as the input.setContentType
: Since we are transforming the content type of the data fromapplication/json
totext/xml
, we can set the new content-type header here.
The response transformation below is similar to what we’ve just shown, but in reverse.
- The
xml-to-json
XSLT function translates the XML response from the server to the JSON that we see in the response payload. - The
setContentType
attribute identifies that we’ll be serving JSON in the response instead of the XML returned from the upstream service.
You can inspect the full VirtualService
with both the XSLT request and response transforms in GitHub. Let’s apply these changes to our VirtualService
and test.
You should see a response like this to indicate the VirtualService
has been modified.
We will use curl
with a new JSON request body to confirm that our change was successful.
You can see that we are now receiving a JSON payload instead of XML in response to our request.
Establishing the initial portal API
At this point, we are using Gloo Edge with an XSLT transform to access our SOAP service in a REST/JSON style. But how do we bring it under management by Gloo Portal? That’s what we’ll cover in this section.
There are three Gloo Portal components we’ll configure here:
- An
APIDoc
to wrap the OpenAPI interface that we created earlier for the World Cities application; - An
APIProduct
to apply policies to the service endpoint in the OpenAPI specification and package it for distribution; and - An
Environment
to declare which APIProducts will be deployed to which compute environments (e.g., dev, QA, production).
The APIDoc
is quite simple; basically just defining a schema that points to a URL containing the OpenAPI interface already built.
The initial APIProduct
is quite simple as well. It makes all of the APIDoc
endpoints—only one in this case—available to the portal and routes all requests to the world-cities Upstream
service. Its most prominent features are the XSLT transformations we explored earlier to translate the request and response payloads to and from XML and JSON. These XSLTs are identical to the ones we tested earlier using only Gloo Edge. You can inspect the full APIProduct
we’ll be using here.
The Environment
custom resource that we’ll be creating is straightforward. We’re defining a single “development” environment hosted at api.worldcities.example.com
. For now, its only function is to designate which APIProducts
it will be publishing.
Let’s apply all three of these portal components together.
You should see a response like this:
Once these portal custom resources are in place, the product has enough information to publish the API for consumption. We have not yet published the Portal
CR, so the web UI sandbox is not yet available. But we can test the underlying API.
What happens is that once the APIDoc
, APIProduct
, and Environment
are in place, Gloo Portal publishes a Gloo Edge virtual service that allows traffic on the routes specified in those components. We can observe that VirtualService
using kubectl
:
You should see two VirtualServices
returned:
The world-city-service-vs
was produced earlier in this exercise when we built the entire VirtualService
by hand to test the upstream SOAP service using just Gloo Edge. Now Gloo Portal has published the dev-world-cities
service automatically to the Gloo Edge platform. (Note that the name of the portal-produced service, dev-world-cities
, matches the name of the Environment
for which it was produced.)
We will first delete the original hand-built service due to potential conflicts with the new Portal-produced service, and then test the new one.
You should see a result like this:
We will now test the portal-produced VirtualService
:
You should see that the new service works as expected:
Removing a final SOAP interface vestige
You may have noticed that one final vestige of the original SOAP interface remains. Specifically, there is a required SOAPAction
header that you can see in the earlier curl
commands.
What we’ll do in this section is add an early-stage transformation to inject that header rather than require it to be specified in the external request. So there will be two sets of transformations applied to each request:
- Inject the
SOAPAction
header; and - Apply the XSLT header to translate the request and response payloads from JSON to XML and vice versa.
In addition to adding this transformation, we will also apply a change to our OpenAPI specification and its associated APIDoc
to eliminate the need for this header in the application interface. The OpenAPI change simply eliminates the SOAPAction
parameter from the interface specification. You can see the eliminated stanza here. We’ll also modify the APIDoc
for our interface to point to the simplified JSON OpenAPI specification.
The extra transformation to inject the SOAPAction
header automatically is available here. We will modify the APIProduct
to add that stanza.
We’ll use kubectl
to apply both of these changes.
You should see this response.
Finally, issue this modified curl
command without the SOAPAction
header and observe that the response is identical to what you saw earlier.
At this point, we have a working portal API that shows a REST/JSON interface to the outside world based on a proper OpenAPI specification, while maintaining the upstream service in its original SOAP form.
Producing a portal interface
While we now have an API generated by Gloo Portal, we do not yet have a web interface where developers outside the original team can more easily consume the published interface. That’s what we will tackle in this section.
The core of the interface specification is the Portal
custom resource. It contains basic textual titles and descriptions, plus an optional set of images that are vital if visual branding is important to your project, and also a list of Environment
CRs available from this Portal
. You can optionally specify links to user documentation that will be displayed with the portal, along with more sophisticated CSS customizations for users where more precise control over branding is required.
We will specify a basic Portal
interface for our world-cities
application.
Let’s use kubectl
to apply this Portal
CR:
Expect to see this response:
We’ll also install a bare-bones User
and Group
to our environment so that we can access the portal. In your real environments, you’ll likely want to delegate authentication decisions like this to a proper IdP. That’s beyond the scope of this post, but you can learn more about it here.
This kubectl
command will install a single User
with username dev1
and password Pa$$w0rd
. It will add the dev1
user to a Group
called developers
and will assign our Portal
to the group. You can see the details of this configuration here.
You should see that the Group
, User
, and a Secret
to hold the user’s password are all created.
Fixing CORS errors with Gloo Portal and Gloo Edge
Now it should just be a simple matter of navigating to the portal’s web address and logging in. Then we should be able to exercise our RESTified SOAP interface. Right?
Try it out by navigating a web browser to http://worldcities.example.com/
and logging in using our username/password of dev1
/Pa$$w0rd
. Select the “World Cities Product” API and expand the lone POST
endpoint.

Now try to “Execute” that endpoint.

You can see that invoking that endpoint failed, and that a few possible reasons are listed. We used Chrome developer tools to help isolate the cause. They show us in the snapshot below that there is indeed a CORS (Cross-Origin Resource Sharing) error when the web browser tries to invoke the endpoint that we curl
ed earlier at api.worldcities.example.com
.

The issue is that the injected SOAPAction
header is something that is specific to our application. It isn’t one of the headers that the portal configures by default to allow it to pass from the web interface to the API. That’s why we see the CORS error.
No worries, though. Gloo Portal and Gloo Edge allow us to specify additional headers that we will allow to flow between the web interface and the underlying API. We will make corresponding changes to both our APIProduct
and Environment
to add SOAPAction
to the list of allowed headers. This is how the change appears in the Environment
custom resource:
Let’s use kubectl
to apply the CORS changes to both the APIProduct
and Environment
.
You should see a response like this:
Now if you establish a new session with the portal, you should see that our new interface to our SOAP service works exactly as expected.

If you have made it this far with us, then congratulations! You have successfully taken a SOAP service endpoint under management, converted it to a REST interface, published it to outside developer groups, and then consumed that interface using the generated Gloo Portal web interface. And you’ve done all that with YAML and XSLT configuration that lives completely outside the managed application. Impressive work!
Beyond SOAP applications
In this blog post, you’ve seen that you can keep running your SOAP applications while modernizing your connectivity, giving you a more robust operating environment so that you can upgrade or replace them when you are ready. In this case, we walked step-by-step through the process of building out a World Cities portal that transforms a SOAP/XML service into a proper REST interface modeled with the OpenAPI standard. And we did all of that with external configuration that did not require us to touch the underlying SOAP service. All of the code used in this post is available on GitHub.
We are just scratching the surface of features we could deploy to this portal. For example, in a perfect world we might want to apply rate limits to our interface and require its users to generate API keys so that we can better track and control their usage. Creating a Usage Plan is an easy way to add these capabilities. We might want to take other types of services under management, like those with a gRPC interface. We might want to explore Gloo Portal’s services to help with monetizing our APIs. We might want to explore managing our Gloo Portal artifacts with an administrative web interface in addition to the YAML-based configuration we used in this exercise. Finally, we might want to integrate with a proper IdP that supports the OIDC standard. All of these capabilities and more are available with Gloo Portal.
For more information, check out the following resources:
- Learn more about SOAP and XSLT with Gloo Edge.
- Explore the documentation for Gloo Portal.
- Request a live demo or trial for Gloo Edge Enterprise with Gloo Portal.
- See video content on the Solo.io YouTube channel.
- Questions? Join the Solo.io Slack community and check out the community #gloo-portal channel.