views:

871

answers:

6

I am working on a Web Service where the user would input parameters and have the option of have the data returned in various file formats (xml, html (on screen), csv, etc.).

If the server generates the file content (and therefor there is no actual file), then returning a URL to the file is not an option. So how can this be done? I know there are xml attributes to indicate that the data within an element is binary, but that won't trigger the browser to download the data as a file.

Is there some equivalent of Content Disposition for XML/Web Services?

On a very related note:

Can javascript/AJAX trigger a browser to download a file? I know that js alone doesn't have the ability to "generate" files (in the sense that the browser will download a string as a file with a given file name" but what if an ajax function calls a script and the script reply has Headers set to download?

I'll experiment with that last part, but would like to know if there is any solid info already out there.


More Details:

First, thank you to everyone who has responded. All of the answers have been really helpful and educational.

I thought I might try to explain a bit more about the situation to tie up all of the comments/concerns/suggestions that span the answers.

I manage a project for an internal staff web site. The project deals mainly with schedule viewing and modification. There are at least 4 different PHP scripts which query a MySQL DB for essentially the same data. The queries vary slightly based on the context. In one case it pulls a daily schedule which groups by the assignment, in another it queries a full week's schedule and groups by person. In yet another it queries for just one person's weekly schedule. In some it outputs data like hourly totals per person or day or position, and in others it just gives what is stored in the DB.

I have started to feel guilty about certain aspects of the project. Obviously I can combine a lot of the data fetching into one included script. I probably ought to be using object-oriented PHP to handle the results. And I have been more focused on making this mess work than making sure the data is safe and secure and that the stress on the DB is minimal and optimized. I'm not even sure how to really know if the queries ARE optimized.

And in addition to the above mentioned guilt, I've really started to get confused. In trying to neaten things up, I've found myself tangled up in a knot where one fix screws up something that was working fine.

So, I thought a good solution would be to create a Web Service that would simplify the entire process and force me to have a logic (an API, even) to how the data is fetched, and would be more useful for anyone who tried to take over when I eventually am not the lead developer. Plus it would make the whole thing more portable, for both upgrades and shifts in the site's back-end (some day it might be on a Windows server using Cold Fusion but I shudder at the thought) and for other software that may want to connect with the database.

But here's the rub:

The entire genesis of this project was based on the fact that our previous scheduling solution was to upload the very ugly and non-semantic HTML report generated by the very expensive enterprise-level scheduling software we use. The number one problem we had with this hideous HTML report and the software that generated it was that there was no option for a .ics "icalendar" file that we could import into Outlook or Google Calendar. I made it my personal mission to figure out a way to turn those awful reports INTO ics files, and thus begins the story on why I started learning PHP, regex, and MySQL.

So, if you aren't bored and asleep yet, I think a Web Service is the best option for simplifying and streamlining the schedule process, but if I end up having to use plain PHP/MySQL to get the most valuable part of the data (the calendar files) then I'm inclined to just ditch the whole thing and keep untangling the knot.

I do understand, by the way, that the script that uses the Web Service can create the ics file and that the AJAX can simply be avoided when that is what the user is requesting. Again, thanks to all that have made this more clear to me. I just want the web service to handle everything returned by the DB as possible on its end to have a sense of a clear partition between the HTML/JS/PHP and the database.

Thanks again!

A: 

I'm afraid that will not work. AJAX calls doesn't pass their response headers to the browser. The only thing you can if you change the headers is that your AJAX call may fail because the headers won't be recognized.

The only solution I see is to return a URL to a file and open new JS window that will trigger that download.

RaYell
Okay, so what if the Web Service is meant to abstract the file system from the user. Should the script create a copy of the file to a temporary safe directory that users can access or should there be mod_rewrite rule that handles file requests?
Anthony
Can be either I guess.
RaYell
A: 

Return a byte array from the web service. The same if you want to send files to the service.

Shaun
But doesn't all data returned from a Web Service come back as XML? Won't the XML wrappers screw up the file upon download?
Anthony
Depends, I suppose, on the _type_ of web service, which you didn't specify. I take it by this you mean SOAP? If yes, then let your JS call a "page" that in turn calls the service, setting the appropriate mime type before writing to the response buffer. Simple.
Shaun
Sorry, SOAP is the only type of WS I have messed around with, so I thought XML was a cruicial part of how a Web Service was different from just any-ol script.
Anthony
Most of the code that is used to call a web service is built on a framework that hides the XML wrappers from you. Such code would call the web service and receive a byte array, not XML. If you're using an environment that does not hide the XML, then, yes, you'll have to parse the XML.
John Saunders
A: 

In a typical browser environment, JavaScript cannot (for security reasons) access the clipboard or the filesystem. For some discussion see clipboard-image-directly-save-in-to-client-side-using-javascript, or similar questions.

There are exceptions, for example can-javascript-access-a-filesystem.

gimel
A: 

The point of Web Services is that they are not meant to be accessed directly by a browser - they are an API for other web applications to use. Thus you should probably write a user-friendly front-end which calls the web service and serves up the data as a file within a HTTP response. That will trigger the browser to download it as a file.

Tom Woolfrey
So this means that the browser (and the front-end you suggest) need to take a more active role, it seems. If the data to be returned will be on screen, that page passes the request via AJAX and returns the response (which is then output to the screen). But if the data is to be downloaded, the request goes straight to the server (via a form action) and thus returns an HTTP response, right? Or are you saying that AJAX and Web Services can't mix? I don't intend to make the web service directly interfacable to the browser. But at some point a connection is made, so AJAX may be involved.
Anthony
AJAX and web services probably *can* mix, but I think as good practice they *shouldn't*, in the sense that AJAX shouldn't be used to directly call the web service. You could have a CGI/PHP script or some such that gets called directly from the browser, and in turn calls the web service and returns a HTTP response in the correct format (file, HTML etc). Thus the returned HTTP Content-Type header will instruct the browser to either download (for a file) or display (for HTML) the content. Hope this helps.
Tom Woolfrey
AJAX is frequently used to call web services, which may return XML, JSON, or even HTML.
John Saunders
A: 

Are you sure you want this to be a web service? How about a normal web page, with form fields for the parameters, which just returns the data as a file, with the appropriate content-disposition?


Based on your update, I'm pretty certain that you do not want a web service to return the .ics file. What you want is a script that will accept some parameters using the query string, and which will then return the file, using the "Attachment" content-disposition. This will cause the user to be prompted to open the .ics file.

Now, you also have the great idea to refactor your several similar scripts to reduce redundancy and maintenance. However, I'm concerned about what you said about every change breaking something. Before you start refactoring, you need to build yourself at least a small test suite, preferably automated. That way, you'd run the tests after every small change, to see if you'd broken anything yet. This way, when you break something, you can only have broken an hour's worth.

I would recommend refactoring the PHP scripts using included scripts or whatever mechanisms PHP has for reuse (I don't know PHP). You may then find that some of the common code would be appropriately implemented as a web service.

John Saunders
+1  A: 

To have the browser download the file, you can do this:

window.location = "http://example.com/webservice-file-url";

And use the header:

Content-Disposition: attachment; filename="filename.ext"

If you want the user to choose between viewing on-screen and downloading, just use a conditional:

if (requested_format == 'html') {
    //get an html version and show it on screen
    ajaxRequestForHTML();
} else {
    //download the file
    window.location = "http://example.com/webservice-file-url";
}
nicholaides