Can a canister return generated xml?

For my project, a host for rss feeds for podcasts, an xml file needs to be returned containing links to media determined from the data in the canister.

But I can’t figure out how to get a canister to return generated xml that a podcast player can parse.

A normal canister won’t work because it only returns data in Candid format, which podcast readers can’t parse.

An asset canister won’t work because, as far as I know, it must go through webpack or can only return static assets. I need to generate the xml on the fly based on the data in the canister and the values in the url. I tried using Mithril for simple routing/url parsing, but because it still goes through webpack I can’t serve just the generated xml. It’s always polluted by the single-page-application scaffolding that webpack forces upon me.

Is it possible to get just a generated xml feed out of the IC?

1 Like

Maybe I’m misunderstanding your question, but can’t you just create a query function in a canister that creates your XML document and returns it as a string? You could then call that function from your frontend JS to further process (parse) it.

Those were my thoughts exactly, but I ran into a couple problems with that solution.

Say I have the following query function:

public query func getXml(): async Text {
    return "<?xml version='1.0'?><rss xmlns:itunes=...";
};

Instead of getting back just the xml, I get back the candid string including the parenthesis and double-quote:

dfx canister call myCanister getXml
("<?xml version='1.0'?><rss xmlns:itunes=...")

That’s not going to be parsed successfully by all podcast players. The xml needs to start with <?xml not ("<?xml.

Even if it did, I don’t know how to get the results of that function just by fetching a url in a browser or curl command. What’s the syntax for calling a canister function by specifying the function name and parameters in the url, similar to how you can use dfx canister call in the terminal? Is that even possible?

I was hoping I could discover the url format by loading up http://127.0.0.1:8000/candid?canisterId= for my canister and pressing the Query button under getXml(), but that sends off a POST request to http://127.0.0.1:8000/api/v1/read with an unintelligible payload:

ÙÙ÷£gcontent¦cargFDIDLkcanister_idRningress_expiryeO!¨¬í€kmethod_namefgetXmllrequest_typeequeryfsenderXí·ïƒ@¥î›Êç0„¶ÆäêKÉð‚msender_pubkeyX óæ#
íü8®Šª@"çǟ†­å´ÞÀÚb¥M¤jsender_sigX@¹¹ä´›µ‰eÎó™yoRŒä\9±žÆÇ'¨y9½D‚–>‘ùëȁpŠ|™ggàóK\\]´0í…Ó<N€(Á

Surely that’s not how I’m supposed to interact with my canister via curl. Is it possible to do so?

I haven’t been able to get the latter idea to work either, using frontend JS to parse and return just the xml. This may be because I’m inexperienced with webpack, but my understand is that the entire SPA gets bundled up into a single JS file. Routing inside the SPA, through React or Mithril or whatever, can do everything I need in terms of parsing the url and preparing the correct xml to return, but it can’t return just the generated xml file. It can modify the DOM and insert the xml content into the body of the page, but that’s not what I need. Again, I have little to no experience with webpack, but afaik it’s impossible to get it to respond to a request with just plain xml, unless that xml is a static file declared in webpack.config.js, which of course it isn’t.

1 Like

But here you are calling from the command line, I believe calling that same function from within your frontend will return just the string. Even if not, you should be able to process the string to get rid of those ”candid extras”, right?

But from what you are saying it seems that it’s not that trivial and I am in no way an experienced frontend developer :slight_smile:

I’m not sure how to return “just the string” from frontend code that goes through webpack. I can access the string and process it, but then what? How do I return a page that consists of only that xml string, with no surrounding html? Maybe there’s a way; I’m new to webpack too.

At the moment, a canister cannot serve plain HTML or XML response. If you really want a plain text to be returned from a canister, you can try the proxy solution given by @nometa in another discussion thread. EDIT: just realized that thread was also a reply to your questions :slight_smile:

We are aware that serving plain HTML (or data encoded in other HTTP content type) directly from the canister end point is a popular demand. But the JSON format at the moment contains extra data besides the return value of calling a method, e.g., signature and proof that can help verify the authenticity of the return value.

1 Like

@kpeacock Do the recently improved asset canisters make it possible to return generated XML now?

I think that, with the dependency on Webpack removed, I should be able to return a page that consists of only my generated xml string, with no surrounding html like webpack was surrounding my page with.

I can put a static rss file under my_canister_assets/assets and access it via url. That works fine and I get back just my xml as expected. But if I want to generate the xml file based on information in the canister, I need to use javascript and because of that, I still end up with some html surrounding the xml I intend to return. For example, if index.html is set to run a javascript file on load like so:

(I keep getting 403 errors trying to post this, so I’m splitting it up into multiple posts. Sorry.)

I couldn’t post the html here, even in a triple backtick section. Here’s the code in pastebins:

my_canister_assets/src/index.html
https://pastebin.com/raw/453VRQLs

my_canister_assets/src/index.js:
https://pastebin.com/raw/VNzGQnks

When I deploy and navigate to the page, I get back the following, with the rss tag buried under the unnecessary and bothersome html/body tags.

Is it possible yet to have a canister return pure generated xml without going through a solution hosted on aws or somewhere else like @nomeata’s ic_http_bridge? @paulyoung mentioned implementing http_request in this post, but I can’t find any hints on how to do that in motoko. I realize there’s an ongoing effort to support update calls in this way; I’m fine with query calls for now.

Thanks, and sorry for the three-post question!

You can. @lastmjs does this here: https://ic3o3-qiaaa-aaaae-qaaia-cai.raw.ic0.app/

Here’s an example of implementing http_request in Motoko that I wrote: feat: infer update call based on HTTP request method by paulyoung · Pull Request #195 · dfinity/agent-rs · GitHub

2 Likes

Actually, I’m not sure if @lastmjs serves the RSS feed from the IC but it’s definitely possible.

I’ve considered it as a way to provide notifications for users but had some concerns around the costs of cycles from the constant polling.

To be totally clear, I’ve only done this via my forked version of icx-proxy running locally.

I have reason to believe this works when making a request directly to the replica/network without a local proxy but for certain reasons I haven’t actually done that yet.

I definitely do host the RSS xml file from the IC, it’s awesome and is working great so far

2 Likes

I also just tried making a request directly to the local replica and it worked as expected, although it’s still not clear to me if this is due to icx-proxy being run locally in the background on my behalf and if it works exactly the same way when deployed to the IC.

Yeah, you can totally serve an RSS feed from an asset canister. Not a big deal at all

1 Like

I’m getting an error when testing this on a canister deployed to the IC. Has anyone successfully gotten the http_request method to work on the IC?

@paulyoung Using your code as a reference, I was also able to serve my RSS feeds when running dfx locally. But when I deploy to a canister on the IC and load the .ic0.app page, even with a simple body like yours, I get back:

Body does not pass verification

The error is coming from line 315 in validation.ts, but there are a lot of things that could cause validateBody to return false and the error gives no hint.

Has anyone gotten a deployed app to return a successful response from the http_request method?

@kpeacock I’m deploying it as a motoko canister with an http_request method that returns my generated xml, not as an asset canister. AFAIK I can’t return generated xml from an asset canister.

You’ll need to use the raw.ic0.app address. The ic0.app address expects you to to be serving certified assets, which go through a custom service worker

3 Likes

Phenomenal, that works great!

Could you clarify on what the difference here is? .ic0.app expects to point to an asset canister serving static assets and .raw.ic0.app expects to point to a canister with an http_request function?