Send data to web browser without writing to disk

My first post, I will try to abide by the rules.

I am converting a Flash application to AIR. One feature we use is navigateToURL to run reports. The tool we use accepts POST variables and creates a stream of data which renders a PDF report. In reading through everything I can find the navigateToURL does not support POST, it converts it to GET. This causes a problem.

I have implemented a “File” object, using the “download” method, and it retrieves the data stream and allows the user to choose where to save it and then I have code that automatically opens the file with the default application. What I would prefer is to accept the data stream and send it directly to the default browser with the appropriate mime type, so that the browser can open it.

I have been unable to find anything which indicates this is possible in AS3/AIR. If it is possible I would appreciate a pointer to the documentation, and/or examples on how it can be done.

1 Like

Hello @aceinc and welcome to the forum :slight_smile:

If you look at navigateToURL() documentation, it does support POST but under condition

eg.

In Flash Player 10 and later, if you use a multipart Content-Type (for example “multipart/form-data”) that contains an upload (indicated by a “filename” parameter in a “content-disposition” header within the POST body), the POST operation is subject to the security rules applied to uploads:

  • The POST operation must be performed in response to a user-initiated action, such as a mouse click or key press.
  • If the POST operation is cross-domain (the POST target is not on the same server as the SWF file that is sending the POST request), the target server must provide a URL policy file that permits cross-domain access.

Also, for any multipart Content-Type, the syntax must be valid (according to the RFC2046 standards). If the syntax appears to be invalid, the POST operation is subject to the security rules applied to uploads.

and it is the same for sendToURL()


That said, both methods navigateToURL() and sendToURL() are underneath using an HTTP client to send those requests.

So even if I don’t fully follow what your tool is doing, I would ay the best bet is to implement your own HTTP client based on Socket and docs related to HTTP and POST.


OK so I got few questions.

The AIR app connect to an HTTP server, and send POST request

  • does the request is user-initiated ? eg. someone clicking a button
    or do you want it to behave more like an automated process ?
  • what kind of data are you sending ?
    just variables? JSON? bytes?
  • and finally do you need to receive back data?
    you can send a POST request as “fire and forget” without caring about the returned response
    or you may need to “validate” or “proof of receipt” by getting a response

That’s where I get lost, in which direction the data transfer occurs?

Is it your AIR client requesting a server and you want to receive a stream of data?
Or the AIR client waiting some actions/events to send back a stream of data?

Assuming the case is the following

  • user click in your AIR client
  • the AIR client send a POST request to a server
  • then the AIR client receive the response from that server

All this should work with the API you have in AIR.


Now, building a small HTTP client is not that hard and can allow you more control on how the data is sent.

In short, the HTTP protocol is text-based, and in general if you follow HTTP/1.1 there are just few rules to follow.

Look at

First, you have different kind of POST requests

  • application/x-www-form-urlencoded : the keys and values are encoded in key-value tuples separated by '&' , with a '=' between the key and the value. Non-alphanumeric characters in both keys and values are percent encoded: this is the reason why this type is not suitable to use with binary data (use multipart/form-data instead)
  • multipart/form-data : each value is sent as a block of data (“body part”), with a user agent-defined delimiter (“boundary”) separating each part. The keys are given in the Content-Disposition header of each part.
  • text/plain

let’s see few examples

A basic POST request using application/x-www-form-urlencoded
look like that

POST /test HTTP/1.1
Host: foo.example
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

field1=value1&field2=value2

that same request when it is transformed to a GET request gives something like that

GET /test?field1=value1&field2=value2 HTTP/1.1
Host: foo.example

The differences are

  • the data, the variables value-pair
    with a GET request is send the URL path
    with a POST request is send into the body of the request
  • a GET request is limited in the amount of data
    so you should use it only for “small” amount of data
    (usually 2048 characters)
  • with a GET request the data will show in server logs
    for some use case it is not secure to do that

But as long as you want only to send value-pairs and small amount of data,
GET or POST will get you the same result.

Now if you want to send a POST request with multipart/form-data, there things get real different

it will look like that

POST /test HTTP/1.1 
Host: foo.example
Content-Type: multipart/form-data;boundary="boundary" 

--boundary 
Content-Disposition: form-data; name="field1" 

value1 
--boundary 
Content-Disposition: form-data; name="field2"; filename="example.txt" 

value2
--boundary--

You use that to send file(s) usually.

A POST request using text/plain would look like that

POST /test HTTP/1.1
Host: foo.example
Content-Type: text/plain
Content-Length: 20

hello the big world

But all those are in the context of browsers “classic usage”, in the MDN doc the thing to notice is that

When the POST request is sent via a method other than an HTML form — like via an XMLHttpRequest — the body can take any type. As described in the HTTP 1.1 specification, POST is designed to allow a uniform method to cover the following functions:

  • Annotation of existing resources
  • Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
  • Adding a new user through a signup modal;
  • Providing a block of data, such as the result of submitting a form, to a data-handling process;
  • Extending a database through an append operation.

eg. “the body can take any type”

and that’s how you send JSON data from a browser to a server for ex:

POST /test HTTP/1.1
Host: foo.example
Content-Type: application/json;charset=UTF-8
Content-Length: 39

{ field1: "value1", field2: "value2" }

In short, the Content-Type is what tell the server which type of data you are sending, and you could send raw binary data if you wanted with Content-Type: application/octet-stream.


So in AS3 and AIR, you have many options to manage POST requests

Alternatively you can also look into
Corsaair/httplib HTTP Library

It is more server-oriented, you will have to refactor the socket part in HttpConnection
but HttpRequest should be easy to follow.

It is a bit old, but it got the essentials.

Thanks for the detailed response.

navigateToURL does precisely what I need in Flash, but in AIR they have disabled the “POST” capability, silently changing all POST variables to GET.

In my applications there are many places where reports are generated. Some are from a click event, others are after a long chain of actions, and some are run straight from a menu item.

My Flash code looks like;

	    var args:URLVariables = new URLVariables();
		args["rs:SessionID"] = this.parentApplication.intSessionID.toString();
		args["rs:url"] = "Reports/EmpAnalysis.rdl";
	    args["rs:Format"] = "pdf";
	    args.CompanyName = this.parentApplication.xmlCompanyInfo.ACECompany.ACECOMP_NAME;
	    args.EmpNbr = EMPLOYEE_NUM.value;
	    args.EmpName = EMPLOYEE_NUM.text;	    
	    args.StartDate = EmpInitialDate.text;
	    args.EndDate = EmpTerminalDate.text;
	    args.Prepare = "Prepared for " + this.parentApplication.xmlCompanyInfo.Employee.EMPLOYEE_FIRST + " " +
			    						 this.parentApplication.xmlCompanyInfo.Employee.EMPLOYEE_LAST + " on ";
	    var url:URLRequest = new URLRequest(RootDomain + "ReportServer/ShowReport.aspx");
	    url.method = "POST";
	    url.data = args;	    
	    navigateToURL(url,"_blank");

My new AIR code changes the last line to;

_objAppUtils.getFileFromURL(url,"EmployeeAnalysis.pdf");

And _objAppUtils.getFileFromURL looks like;

		public function getFileFromURL(urlToGet:URLRequest, strDefaultFileName:String):void
		{
			_objDownloadFile = new File;
			_objDownloadFile.addEventListener(Event.COMPLETE,fileCompletionHandler);
			_objDownloadFile.download(urlToGet, strDefaultFileName);
		}

		private function fileCompletionHandler(event:Event):void
		{
			_objDownloadFile.removeEventListener(Event.COMPLETE,fileCompletionHandler);
			var strFilePath:String = _objDownloadFile.nativePath;
			var file:File = File.applicationDirectory.resolvePath(strFilePath);
			file.openWithDefaultApplication()
		}

Typically the data being sent is name value pairs, (name1=value1)

The report generator sends back a properly mime typed data and does not require an “ACK”.

The Flash application using the code above, gets information from the user/app, sends a request off to the report generator using POST, it then pops up a new browser window/tab (_blank) with the pdf report in it. I would like to emulate this, however when I try to use navigateToURL in Adobe AIR, all of the name/variable pairs appear on the url (http://xyz.com/report.aspx?argument1=value1&Argument2=value2…).

If there is a way in AIR to send POST variables, and have it pop up a default browser with the results I would be happy with that.

If not, my thought was to use some other oject which implements the URLRequest, capture the stream of data, and then send the stream of data to the default browser. It will already have the appropriate HTML/mime stuff in the data stream. I do not know what objects & methods are needed to implement this.

Since No one had an answer, what I implemented was to create a temporary folder when you first “print” (create a pdf) something. I automatically download the pdf to the temporary folder and fire up the default pdf viewer.

When I close the app I delete the temporary folder and contents. When I start the app, I look to see if the folder exists and delete it before I start. It isn’t perfect, but it gets the job done.