PHP Proxy


Our strategy to defeat Macromedia's domain perimeter policy is to create a server script that resides in our domain space. This script is not limited by the Macromedia restrictions and can contact other servers on any domain in the world. It relays requests from Flash to other servers and echoes back to Flash the foreign servers' responses. This process is called a proxy service.

Our script will accept XML of any sort from Flash. It will contact a server using either GET or POST methods . The XML it receives from the server is repeated back to Flash without any server processing.

The client (the Flash code making the request) must direct the proxy server to the foreign URL that it is trying to contact (Figure 18.1). The client sends that information in the XML package that it uploads to the proxy. This XML object has one required element, <proxy-target> , which contains the full URL of the external XML resource. It also has an optional second element called <request> . This element is used when the Flash client is uploading material to a remote server. The proxy will POST the entire contents of the <request> element without altering (or even parsing) that block of data.

Figure 18.1. The Flow of XML Across our Proxy

graphics/18fig01.jpg

Code

Our server script performs four XML transactions:

  • Receive an XML package POSTed by Flash

  • Send the <request> element from that package to the URL marked as <proxy-target>

  • Receive an XML response from the server

  • Echo the XML response unchanged to Flash client

It is only the first transaction where the script must actually understand the content of the XML. In the other cases, the XML is a black-box payload that is simply delivered to its destination.

But to get from the first transaction to the second, it is necessary to isolate the contents of the <proxy-target> and <request> elements. We could set up and run PHP's event-driven parser. But it is quite a bit of work for a small task. Instead, we dissect the XML stream ourselves using string processing methods.

We begin by defining a function that yanks the content of an element out from a string of XML.

PHP
 <?php function getElementContent( $xml, $tag ) {        $start = strpos( $xml, "<$tag" );        $start = strpos( $xml,">", $start )+1;        $end   = strpos( $xml, "</$tag>", $start );        if( ($start===false) or ($end === false)) return "";        return   substr( $xml, $start, $end-$start); } 

Three notes about this function:

  1. The parameter $xml refers to an XML code string, not an XML data object. In fact, unlike ActionScript, JavaScript, Java, and ASP, normal PHP 4.0 does not have an XML object.

  2. The end tag is found with a single strpos call. The start tag, however, is found with two calls, one to locate the first characters of the start tag, the second to find the tag's terminator (the > ). This is done to skip over any attributes that may exist in this element. Currently no attributes are defined, but a later version might well place attributes on the proxy-target element (perhaps authorization information or cookies). By anticipating this possibility, we can maintain some forward compatibility and build a more robust system.

  3. The test ($start===false) uses a triple equals sign. This formulation is unique to PHP (starting with PHP 4). It requires both sides to resolve not only to the same value but also to the same type. This is extremely useful here: When strpos finds the search string it returns the position of its first character. When it cannot find the string at all, it returns a boolean zero ”false.

    The problem arises when the search string is found at the beginning of the text. A value of 0 is returned by strpos , meaning that you'll find the target string starting at position 0. We cannot easily distinguish this integer 0 from the boolean 0, which means "target string not found." The identity conditional ”the triple equals ”does exactly that.

PHP
 $msg= $GLOBALS[ "HTTP_RAW_POST_DATA" ]; 

First we select the text directly from the input buffer. Or we use a fake line.

PHP
 if( strlen($msg) <36) $msg  ="<xml><proxy-target>http://p.moreover.com/     cgi-local/page?c=Online%20games&o=xml</proxy-target></xml>"; 

The fake input is just used to test this script as it is developed. However it shows us a typ ical data package. We see that within the basic <xml> object there is only the single element <proxy-target>. This sample does not have a <request> element. Indeed if it did, there would be a serious paradox.

If present, our <request> element is an XML string that our proxy must deliver to the server with the POST method. But the <proxy-target> here is a URL to which a string of x-www-urlencoded equations is joined, setting c to "Online games " and o to "xml" . Presumably these set the content and output format of the response. This URL clearly suggests the GET method, rather than POST. In fact, a POST to this server location returns

Output
 Error 405: Method Not Allowed. 

Now we crudely parse our XML string.

PHP
 $target   = getElementContent( $msg, "proxy-target"); $dblslash = strpos( $target, "//" ); $target   = substr( $target, ($dblslash === false)? 0 : $dblslash+2); 

The proxy target is isolated, and any prefix is stripped off. For instance, we want to convert "http://bigfun.net" to "bigfun.net".

The "http:" prefix to the URL instructs the browser to use the HTTP protocol. This PHP script runs on our server with no browser or any other HTTP intermediary. Instead, we learn to open up a socket directly and manually execute the steps of an HTTP transaction. The "http:" prefix does not help us, and it gets in the way. Socket software requires simple domain names : awl.com, not http://awl.com.

Note that we used triple equals again to distinguish the $dblslash result of "awl.com" (boolean 0) from that of "//awl.com" (integer 0).

PHP
 $slash   = strpos( $target,  "/" ); $domain  = substr( $target, 0, $slash); $page    = substr( $target, $slash); 

Our implementation of HTTP requires one procedure for contacting the server and a separate function for requesting an individual document. So we need separate strings for the domain and the page names. We therefore split the URL at the first slash (which becomes the leading char acter of the page name ).

PHP
 $request= getElementContent( $msg, "proxy-request"); 

We snip the request XML out of the raw string. Remember, when composing this package, that <proxy-request> only is meant for packaging the request and passing it to our server script. The tags are stripped from the XML before the code is transmitted to the remote server. But the XML sent to the remote server must be well formed : it must be contained in a single element. You cannot expect the <proxy-request> element to serve that purpose. Instead, use a wrapper element that is expected by the remote service.

PHP
 $method = ((strlen($request)<7)strpos( $page, "?"))? "GET":"POST"; 

If no real XML request package exists or if a URL-encoded message is found, we use the GET method. Otherwise we use POST.

Now we have set up all the parameters we need to contact the remote server on behalf of the restricted Flash program.

Sockets

The Unix concept of pipes was a model in which the output of one process is routed to the input of another. It was a liberating idea, and a lot of interesting software has been written to participate in the piped streams.

Underlying the original pipes was a sense of data flowing one way ”from source to sink. Pipe functionality has been stretched to accommodate more complex pathways , but it has given birth to a newer way to manage interprocess communication. The socket concept is more flexible, more platform independent, and better suited to processes running on remote machines. Sockets form the plumbing of the Internet.

Every machine on the Internet has its own IP (Internet Protocol) address. The address is of the form 209.122.43.129 and uniquely identifies a given computer. If this were the phone system, it would end there. Users would be expected to memorize (or record) a long string of digits for each person they wish to contact.

But the Internet offers a convenience. There are servers in the Internet dedicated to looking up IP addresses automatically. These are DNS ” domain name servers. We type in "usa.net" and we connect to 204.68.23.29. If only Ma Bell had thought of this! In hindsight the telephone network seems so crude and so poorly adapted to human needs.

Each IP address is further divided into ports. The term sparks a nice mental picture of a physical connection, like a phone jack or a serial port, but the relationship between a port and anything physical is fairly tenuous. Better to think of it as a completely abstract mechanism for sorting incoming messages into separate channels.

Each port on the server can have a single socket associated with it. The socket is a server operating system resource. It is used to allocate the port to a software process running on the server through an action called binding. When a socket binds to software, it is committed to service that software alone. Similarly, when the socket binds with a port, a mutual and exclusive relationship is created. While the binding relationship is an absolute commitment, it is not a permanent one.

Common Internet processes tend to be found on (informally) reserved ports. For instance, a WWW server running the HTTP protocol is always at port 80. The ftp server is found at 23. One port on the server, one server socket, one software process.

However, as we all know, many clients can connect to a single server. Hundreds of browsers can contend for the attention of a single server. This asymmetry is at the heart of Internet traffic and makes server-side sockets programming a complicated art, which we address in subsequent chapters.

Socket programming on the client side is not terribly complicated. A socket can be opened as a stream with a file pointer, and it can be easily written to and read from.

PHP
 $fp = fsockopen( $domain, 80, &$errno, &$errstr, 30); 

This line opens a socket on the alien server at $domain. No traffic has started. There is no HTTP transaction yet and certainly no content exchange. Only the raw Internet Protocol connection has been made. HTTP is a protocol that is transported on top of an IP connection (Figure 18.2).

Figure 18.2. Protocols Support Protocols

graphics/18fig02.gif

The fsockopen() line opens the socket as stream and returns with a file pointer. Now reading the socket can be performed conveniently using the simple and familiar functions used for file i/o. The hardcoded 80 parameter is the port number, which is always 80 for an HTTP server.

The next two parameters send the addresses of variables dedicated to error reporting. Note the ampersand that precedes the variable name $errno. This is the PHP syntax that allows a parameter to be passed by reference, where a called function is passed a pointer that allows it to directly manipulate the calling function's variable. Usually parameters are passed by value, where the called function gets a private disposable copy of the variable and can never modify the original

The final parameter, simply hardcoded to 30 , is the optional timeout parameter. This call will fail if a connection cannot be successfully concluded in 30 seconds.

PHP
 header("Content-Type: text/xml"); 

The PHP command header("Content-type ") transmits a header back to the Flash client that sent the request.

Note carefully : This header is not sent across our new socket. The header() command, (like echo() and print() ) continues to use the original connection. It is not intended for the alien server. Across the new socket, the program at $domain is the server, and our proxy pro gram behaves as the client. Across the default socket the Flash program is still the client, and the proxy is the server. The goal of the proxy is to be transparent. As much as possible, we want the proxy to look like the Flash client to the remote server and look like the remote server to the Flash client.

The reason the proxy is sending the header to the Flash client is to prepare for the next line, which may require the proxy to send an error message formatted as an XML data packet.

PHP
 if (!$fp)   echo ('<ERROR errno ="'. $errno .'" >'. $errstr .'</ERROR>' ); else {   fputs ($fp, $method." ".$page." HTTP/1.0\ r\ n"); 

If there is an error creating the socket, a failure message is sent to the client. Otherwise, a request is sent to the server. The request will be of the classic form:

HTTP
 GET myfile.php?args HTTP/1.0 

If it is a GET request, our job is done. Any parameters that needed to be sent to the server were already incorporated into the URL. They were appended to the end and separated by a question mark.

A POST request is more complex. Our proxy has to compose a fairly complicated HTTP request.

PHP
 if( $method=="POST" ){        fputs ($fp, "Content-Type: application/xml");        fputs ($fp, "Content-Length: ".strlen( $request) );        fputs ($fp, "\ r\ n");        fputs ($fp, $request );        } 

If this is a POST request, our code assembles these pieces

  • The data is labeled as XML data.

  • The size of the data is indicated. This is the length of the XML string within the <proxy-request> tags.

  • A blank line marks the end of the header. (Headers are of variable length.)

  • The XML content follows .

A POST transaction is commonly used when requesting complex services in which the client submits a package of data for processing and receives a fresh XML package from the server as a response.

PHP
 else   fputs ($fp, "\ r\ n"); 

Both GET and POST requests are terminated with a blank line. The end of an HTTP request is signaled in several ways:

  • A second blank line is inserted (the first terminated the header).

  • The Content-Length specified in the header is satisfied.

  • An internal content end marker is transmitted.

  • The socket is closed.

The first way (blank line) works fine for the methods like GET, as exemplified here. But it is unreliable for most POST data and certainly for arbitrary XML, which can include blank lines happily.

The second (content length) is the most reliable for POST data. It is what we use for the request here. Very soon, however, we will see that the content length is not always known at the time of header transmission.

The third method (internal marker) works when the process receiving the transmission is well understood and reliable. An example is the </HTML> tag that ends every web page. Most servers can rely on the browser to recognize this tag. Similarly, the end of an XML transmission is often indicated by the closing tag that finally matches the initial opening tag. This method requires a robust receiving process (for example, the proxy we are now building would fail), and it is quite ugly in the way it relies on a higher layer (HTML or XML) to supply information that is missing from a lower layer (HTTP). Returning to a telephone analogy, this would be as if the only way we could hang up a line is by saying "Goodbye" to someone on the other side who understood us. It usually works but the potential for failure should be obvious.

The fourth technique (socket closure) again uses information in one layer to correct a defect in another. But in this case, the lower layer (socket) terminates a transaction that was left open in the higher layer (HTTP). Although it is not optimal for information to cross layers , socket closure is extremely reliable. Like hanging up a phone call to terminate a conversation, it is crude but effective. It is not unlike other i/o stream reads that are terminated by the end-of-file indicator. We do, in fact, use this technique very soon in our proxy, to mark the end of the response in both the client and server roles. This technique has a major limitation: It is useful only in the response portion of the HTTP transaction. If you were to close the socket after the request portion, you would never get your response!

Now that the request is complete, our proxy waits for data to arrive . This waiting happens in the function fgets() , which does not return until a line of data appears (meaning that it finds an end-of-line or the buffer fills to the specified length ”e.g., 128). The feof() function signals when there is no more data to be had.

 while (!feof($fp) &&  strlen(trim(fgets ($fp,128))))             ; 

This loop gets each line, trims away the whitespace, and checks the length of the string. If the string is not empty, the loop simply tries again. It is seeking the first blank line. By doing so, the code skips past the header.

Ignoring the header is extremely shabby and limits our proxy to a simple demonstration program that allows us to peek across the domain perimeter in the cases where everything goes smoothly. Our stated goal of transparency is difficult to claim when we toss out the header rather than pass its information on to the client.

Even worse , our code does not read the headers. As we explore the very unstable world of public XML servers, we are bound to run into many errors and incompatibilities that we would be wise to observe. Sometimes we will be able only to fail gracefully. Some errors we will actually be able to correct (such as redirection). Remember that in its role as client, the PHP script gets no help. There is no browser involved; we are directly connected to a naked socket, and we are fully responsible for managing the client side of this session.

That said, let's indulge our impatience and raise a quick and cheap periscope to peer over the domain walls. If we like what we see, we can return to build out a robust proxy that reads and respects the headers.

 while (!feof($fp))      echo fgets ($fp,128);     fclose ($fp);     } ?> 

Simply, we echo to our (Flash) client every line we receive from the remote server. When the remote server finally closes the connection, generating the end-of-file signal in feof() , we do the same thing to our own client.



Flash and XML[c] A Developer[ap]s Guide
Flash and XML[c] A Developer[ap]s Guide
ISBN: 201729202
EAN: N/A
Year: 2005
Pages: 160

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net