Published on September 9, 2023.
A few weeks ago, I was developing a mobile app and ran into trouble. The app was sending an HTTP request to a Web API, but the API didn't seem to be receiving the request that I thought I was sending. To troubleshoot the app, I needed a way to see the request that I was sending, and specifically from the perspective of the server that was receiving it.
There are Web sites available that can be used to collect and inspect HTTP requests (such as RequestBin). However, for security purposes, I didn't want to send requests to a server that I don't have control over.
So I quickly developed a Xojo Web 2.0 app. The app analyzes incoming HTTP requests and returns JSON-encoded responses, which include information about what was received (the request's headers, payload, and so on), and what the app found (errors, warnings, etc) when it analyzed the request.
In this post, I'll discuss how the Xojo Web app works and show the responses that it generates for a few requests. I'll include a link that you can use to test drive the app, and a second link that you can use to download the Xojo project file.
For the first example, let's consider this HTTP GET request.
curl "https://analyzer.mnmlsw.com/some/test/path/?abc=123&def=456&xyz" \ -H 'Cookie: Cookie-A=123; Cookie-B=456; Cookie-C='
The request specifies a path (/some/test/path/), includes a query string (abc=123&def=456&xyz), and also includes HTTP cookies (Cookie-A=123; Cookie-B=456; Cookie-C=).
The app's response looks something like this:
{ "request": { "remoteAddress": "127.0.0.1", "method": "GET", "path": "some/test/path/", "mimetype": "text/html", "secure": "False", "headers": { "": "GET /some/test/path/?abc=123&def=456&xyz HTTP/1.1", "Cookie": "Cookie-A=123; Cookie-B=456; Cookie-C=", "Host": "127.0.0.1:8080", "Connection": "close", "User-Agent": "RapidAPI/4.2.0 (Macintosh; OS X/12.6.2) GCDHTTPRequest" }, "cookies": { "Cookie-A": "123", "Cookie-B": "456", "Cookie-C": "" }, "body": "", "queryString": "abc=123&def=456&xyz", "queryParameters": { "abc": "123", "def": "456", "xyz": "" } }, "analysis": { "errors": [], "warnings": [ "Cookie \"Cookie-C\" has no value.", "Query Parameter \"xyz\" has no value." ], "comments": [] } }
The response includes information about the response that the app received, which is available in the "response" object. This includes the remote IP address (i.e. the IP address of the computer that sent the request), the HTTP method used to send the request, the path that was requested, the HTTP headers that were sent, the request body, and more.
For requests that include cookies, the app splits the cookies up into an array of name/value pairs. Notice that the response includes a warning indicating that one of the cookies has an empty value.
The app handles query parameters in much the same way. It splits the query parameters into an array of name/value pairs. And again, if a parameter's value is empty, the response will include a warning about it.
When the app receives a request that includes body content (for example, when a POST, PUT, PATCH, etc is sent), along with a Content-Type HTTP header with certain values, then it will attempt to parse or decode the content.
For requests with JSON-encoded content (i.e. a Content-Type HTTP header whose value is "application/json"), the app will attempt to decode the content. If the content cannot be decoded, then an error is returned in the response.
For requests that include XML content (i.e. a Content-Type HTTP header whose value is "application/xml"), the app will attempt to use the content to create an XML document. If the parsing fails, then information about the error is returned.
For example, consider this request, which includes a well-formed XML document in the request body.
curl -X "POST" "http://127.0.0.1:8080/xml-example/" \ -H 'Content-Type: application/xml' \ -d $'<?xml version="1.0" encoding="UTF-8"?> <note> <to>Team Xojo</to> <from>Tim Dietrich</from> <heading>Thank you!</heading> <body>Thanks for creating a software development tool that empowers us to build amazing solutions.</body> </note>'
The response will look like this.
{ "request": { "remoteAddress": "127.0.0.1", "method": "POST", "path": "xml-example/", "mimetype": "text/html", "secure": "False", "headers": { "": "POST /xml-example/ HTTP/1.1", "Content-Type": "application/xml", "Host": "127.0.0.1:8080", "Connection": "close", "User-Agent": "RapidAPI/4.2.0 (Macintosh; OS X/12.6.2) GCDHTTPRequest", "Content-Length": "242" }, "body": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <note> <to>Team Xojo</to> <from>Tim Dietrich</from> <heading>Thank you!</heading> <body>Thanks for creating a software development tool that empowers us to build amazing solutions.</body> </note>", "queryString": "" }, "analysis": { "errors": [], "warnings": [], "comments": [ "Successfully parsed the XML request body." ] } }
Notice that the response includes a comment indicating that the request body was parsed successfully.
Now consider this slightly different request, which includes a malformed XML document in the request body. (The closing "from" tag is missing the ending >.)
curl -X "POST" "http://127.0.0.1:8080/xml-example/" \ -H 'Content-Type: application/xml' \ -d $'<?xml version="1.0" encoding="UTF-8"?> <note> <to>Team Xojo</to> <from>Tim Dietrich</from <heading>Thank you!</heading> <body>Thanks for creating a software development tool that empowers us to build amazing solutions.</body> </note>'
Here's the app's response.
{ "request": { "remoteAddress": "127.0.0.1", "method": "POST", "path": "xml-example/", "mimetype": "text/html", "secure": "False", "headers": { "": "POST /xml-example/ HTTP/1.1", "Content-Type": "application/xml", "Host": "127.0.0.1:8080", "Connection": "close", "User-Agent": "RapidAPI/4.2.0 (Macintosh; OS X/12.6.2) GCDHTTPRequest", "Content-Length": "241" }, "body": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <note> <to>Team Xojo</to> <from>Tim Dietrich</from <heading>Thank you!</heading> <body>Thanks for creating a software development tool that empowers us to build amazing solutions.</body> </note>", "queryString": "" }, "analysis": { "errors": [ "An error occurred while attempting to parse the XML request body. Error number: 2. Error message: msg:XML parser error 4: not well-formed (invalid token)" ], "warnings": [], "comments": [] } }
The response includes an error message indicating that the attempt to parse the content failed.
The app handles requests with JSON-encoded request bodies in a similar way.
The content of requests that are form URL-encoded (i.e. a Content-Type HTTP header whose value is "application/x-www-form-urlencoded") is also analyzed and processed. In this case, the app will split the content info an array of field name / value pairs.
When the app receives a request that is passing credentials via the Basic access authentication scheme, it will attempt to decode the credentials.
For example, here's a simple request that is passing a username and password via the Authorization header.
curl "http://127.0.0.1:8080/" \ -u 'j.pinkman@breakingbad.com:science!'
The response looks like this.
{ "request": { "remoteAddress": "127.0.0.1", "method": "GET", "path": "", "mimetype": "text/html", "secure": "False", "headers": { "": "GET / HTTP/1.1", "Authorization": "Basic ai5waW5rbWFuQGJyZWFraW5nYmFkLmNvbTpzY2llbmNlIQ==", "Host": "127.0.0.1:8080", "Connection": "close", "User-Agent": "RapidAPI/4.2.0 (Macintosh; OS X/12.6.2) GCDHTTPRequest" }, "authorization": { "method": "Basic", "username": "j.pinkman@breakingbad.com", "password": "science!" }, "body": "", "queryString": "" }, "analysis": { "errors": [], "warnings": [], "comments": [] } }
Notice the "authentication" information in the response's request object, which includes the decoded username and password.
When returning requests, the app includes a "Server" HTTP header that indicates what version of Xojo the app has been compiled with, and what platform is was compiled for.
For example, "Xojo/2023r2 (Linux)" indicates that the app was compiled using Xojo 2023 Release 2, and that it was compiled for use on a Linux-based server.
I've had a lot of success using the Xojo Web 2.0 framework to develop Web APIs and middleware solutions.
It's the "HandleURL" event handler that makes developing these types of apps possible. When requests are received, the event handler fires, and that provides you with an opportunity to evaluate and respond to the request. Returning a "true" value indicates that the event handler was able to process the request, and that no further action is needed. However, if the request is such that it should be handled by the app normally (meaning that it should display a Web page, etc), then the request handler would return a "false" value. This means that you can develop Xojo-based Web apps that are a combination of a traditional app (with Web pages, sessions, etc) and a Web API.
If you'd like to give the app a try, an instance is available here:
https://analyzer.mnmlsw.com
It's worth noting that the app is running on a Xojo Cloud server.
If you'd like to download the Xojo project, it's available here:
https://tdietrich-opensource.s3.amazonaws.com/Xojo/Request-Analyzer.xojo_binary_project.zip
If you run the app behind a proxy server, then the proxy server will very likely add headers to the request. For example, you might see a "X-Forwarded-For" header, which is the IP address that the request originated from. To see true representations of your requests, you should run the app without a proxy server in the mix.
If you have any questions about the app, please feel free to contact me, or post a question to the Xojo Programming Forum.
Hello, I'm Tim Dietrich. I develop custom software for businesses that are running on NetSuite, including mobile apps, Web portals, Web APIs, and more.
I'm the developer of several popular NetSuite open source solutions, including the SuiteQL Query Tool, SuiteAPI, and more.
I founded SuiteStep, a NetSuite development studio, to provide custom software and AI solutions - and continue pushing the boundaries of what's possible on the NetSuite platform.
Copyright © 2025 Tim Dietrich.