Search

Use Wrappers and Proxies for Basic Web Services Tracking

0 views

Why Monitor Web Services at the Stub Level

When a team ships a new web‑service API, the first thing that surfaces on the ground floor is the need to see what’s actually happening on the network. Debugging an endpoint that sits behind a load balancer, a firewall, or a reverse proxy can feel like chasing ghosts. Every RPC call you issue in code turns into an XML envelope, a TCP segment, and a handful of HTTP headers that cross one or more network hops. A full‑blown wire‑level capture tool will give you the ultimate view, but it comes at a price: hardware, configuration, and a learning curve that can stall early development cycles.

In many scenarios, developers already have the plumbing in place to call a service. The service client library, whether generated from a WSDL or hand‑rolled, exposes a simple method on an object that represents the remote service. That method is the only public touchpoint needed to drive a request. If you could inject a thin layer between the developer’s code and that method, you would gain a first‑class window into request/response flows without touching the underlying network stack.

That is the essence of a wrapper: an object that forwards all calls to an underlying subject while optionally doing something before or after the call. The magic happens in the wrapper’s dynamic dispatch logic. In languages that support duck typing or reflection, you can create a wrapper that looks identical to the subject from the caller’s point of view. The caller remains oblivious; the wrapper quietly logs the call, records timing, and optionally validates input or output.

Because a wrapper operates at the application layer, you can target just a single service or a whole fleet of services. If your team runs several micro‑services that share a common transport (SOAP, REST, gRPC), you can implement a single wrapper strategy that works across all of them. The wrapper can be parameterized to send logs to a file, to a syslog daemon, or to a remote metrics server. You can even attach a small UI that renders the logs in real time for the developers who need instant feedback during a debugging session.

In addition to logging, wrappers can expose performance counters. A simple decorator that records start time, end time, and the difference automatically turns a naive RPC call into a performance monitor. The wrapper can then expose metrics via a REST endpoint, or push them to an external monitoring system like Prometheus or Datadog. This approach keeps the monitoring logic close to the business code, making it easier to maintain and modify as the API evolves.

Because wrappers live inside the application process, they don’t interfere with other services in the network. You do not need to re‑route traffic or set up a dedicated monitoring appliance. The cost is limited to a few lines of code and the runtime overhead of a function call – negligible compared to the network round‑trip time. If you need deeper inspection, you can layer the wrapper on top of an existing network proxy, but the wrapper itself remains a lightweight, first‑defence monitoring tool.

Implementing a Logging Wrapper in Python

Python’s dynamic nature makes it a natural playground for wrapper construction. The language’s attribute‑access hooks let you intercept any method lookup on an object and redirect it as you wish. Below is a concise example that demonstrates how to log every SOAP call made through a third‑party library like

Prompt
pip install zeep</p>

Now, here’s the wrapper class. It accepts a service client and a file‑like object for output. The key method is __getattr__, which is invoked whenever an attribute or method is accessed on the wrapper. If the attribute is callable, we return a closure that logs the call and delegates to the original method.

Prompt
import sys</p> <p>from zeep import Client</p> <p>class LoggingWrapper:</p> <p> """</p> <p> Wraps a Zeep service client to add pre‑ and post‑call logging.</p> <p> """</p> <p> def __init__(self, client, stream=sys.stderr):</p> <p> self._client = client</p> <p> self._stream = stream</p> <p> def __getattr__(self, name):</p> <p> attr = getattr(self._client.service, name)</p> <p> if callable(attr):</p> <p> def wrapper(<em>args, </em>*kwargs):</p> <p> self._stream.write(f"Invoking remote method {name}\ ")</p> <p> self._stream.write(f" positional args: {args}\ ")</p> <p> self._stream.write(f" keyword args: {kwargs}\ ")</p> <p> result = attr(<em>args, </em>*kwargs)</p> <p> self._stream.write(f"Method {name} completed\ ")</p> <p> self._stream.write(f" return value: {result}\ ")</p> <p> return result</p> <p> return wrapper</p> <p> return attr</p>

With the wrapper in place, you can instantiate a Zeep client and immediately get logging for free. The wrapper is generic – you can drop it around any Zeep client, any other client library that exposes a service attribute, or even a plain object that implements a known interface.

Prompt
# Example WSDL</p> <p>WSDL = "http://www.xmethods.net/sd/2001/TemperatureService.wsdl"</p><h1>Create the Zeep client</h1> client = Client(wsdl=WSDL)</p><h1>Wrap it with the logging layer</h1> logged_client = LoggingWrapper(client)</p><h1>Make a call</h1> zipcode = "80027"</p> <p>temperature = logged_client.getTemp(zipcode)</p> <p>print(f"Temperature for {zipcode} is {temperature} F")</p>

When you run the script, the console will show the following output (the actual temperature will vary based on the service implementation):

Prompt
Invoking remote method getTemp</p> <p> positional args: ('80027',)</p> <p> keyword args: {}</p> <p>Method getTemp completed</p> <p> return value: 21.0</p> <p>Temperature for 80027 is 21.0 F</p>

The wrapper’s design is intentionally minimal. You can easily extend it to record timestamps, log to a structured format (JSON, CSV), or send metrics to a time‑series database. Because the wrapper operates purely in user space, no network configuration changes are required. Developers can add or remove logging with a single import, keeping the production codebase lean while providing the diagnostic depth needed during development and testing.

Creating a Generic HTTP/SOAP Proxy for Network Monitoring

While a wrapper gives you insight into individual client calls, you sometimes need a global view of traffic that flows through your network, especially when services are called from multiple hosts or languages. A lightweight HTTP proxy that forwards SOAP requests and logs each transaction can serve that purpose. The following example uses Python’s built‑in http.server module to build a minimal SOAP‑over‑HTTP proxy that logs request paths, headers, and payloads before forwarding them to the intended service.

Start by creating a file named soap_proxy.py and paste the following code. The proxy listens on port 8888 by default but can be adjusted by changing the LISTENING_PORT variable.

Prompt
import http.server</p> <p>import http.client</p> <p>import sys</p> <p>import urllib.parse</p> <p>LISTENING_PORT = 8888</p> <p>class SOAPProxyHandler(http.server.BaseHTTPRequestHandler):</p> <p> def do_POST(self):</p> <p> # Read the content length</p> <p> content_length = int(self.headers.get('Content-Length', 0))</p> <p> request_body = self.rfile.read(content_length)</p> <p> # Log request details</p> <p> sys.stderr.write(f"Path: {self.path}\ ")</p> <p> sys.stderr.write("Headers:\ ")</p> <p> for k, v in self.headers.items():</p> <p> sys.stderr.write(f" {k}: {v}\ ")</p> <p> sys.stderr.write("Payload:\ ")</p> <p> sys.stderr.write(request_body.decode('utf-8', errors='replace'))</p> <p> sys.stderr.write("\ ---\ ")</p> <p> # Determine the destination host and port from the Host header</p> <p> host_header = self.headers.get('Host')</p> <p> if not host_header:</p> <p> self.send_error(400, "Missing Host header")</p> <p> return</p> <p> # Split host and optional port</p> <p> if ':' in host_header:</p> <p> dest_host, dest_port = host_header.split(':', 1)</p> <p> dest_port = int(dest_port)</p> <p> else:</p> <p> dest_host = host_header</p> <p> dest_port = 80 # default HTTP port</p> <p> # Forward the request to the actual service</p> <p> conn = http.client.HTTPConnection(dest_host, dest_port, timeout=10)</p> <p> # Preserve the method and path</p> <p> conn.request("POST", self.path, body=request_body, headers=dict(self.headers))</p> <p> resp = conn.getresponse()</p> <p> # Relay the response back to the client</p> <p> self.send_response(resp.status, resp.reason)</p> <p> for k, v in resp.getheaders():</p> <p> self.send_header(k, v)</p> <p> self.end_headers()</p> <p> self.wfile.write(resp.read())</p> <p> conn.close()</p><h1>Start the server</h1> if __name__ == "__main__":</p> <p> server_address = ("", LISTENING_PORT)</p> <p> httpd = http.server.HTTPServer(server_address, SOAPProxyHandler)</p> <p> print(f"SOAP proxy listening on port {LISTENING_PORT}")</p> <p> httpd.serve_forever()</p>

Run the proxy from the command line:

Prompt
python soap_proxy.py</p>

When the proxy is running, configure your SOAP client to use it as an HTTP proxy. In Zeep, for example, you can set the transport to use a custom Session that points to the proxy:

from requests import Session

from zeep.transports import Transport

session = Session()

session.proxies = {"http": "http://localhost:8888", "https": "http://localhost:8888"}

transport = Transport(session=session)

client = Client(wsdl=WSDL, transport=transport)

With this setup, every SOAP call issued by the client goes through your proxy. The proxy logs each request and response to standard error, giving you a transparent view of the raw XML envelopes. Since the proxy forwards headers unchanged, the target service receives the request exactly as it would from a direct client. Likewise, responses travel back through the proxy unaltered.

Because the proxy operates on plain HTTP, it can serve any SOAP implementation - Java, .NET, PHP, or any language that speaks HTTP. You can extend the proxy to add authentication, rate‑limiting, or even a simple caching layer if needed. For environments where multiple services share the same network, the proxy becomes a single point of observability, reducing the need for multiple vendor‑specific monitoring appliances.

In summary, wrappers give you a low‑overhead, code‑centric monitoring solution, while a network‑level proxy offers a broader perspective without touching the application code. Together they provide a robust toolbox for tracking and debugging web‑service traffic at every layer of the stack.

Suggest a Correction

Found an error or have a suggestion? Let us know and we'll review it.

Share this article

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!

Related Articles