<!--- Calculate the URL to which we are posting. For this demo, it will be another page on this ColdFusion server. ---> <cfset postUrl = ( "http://" & cgi.server_name & getDirectoryFromPath( cgi.script_name ) & "target.cfm" ) /> <!--- Create an instance of our Java URL - This is the object that we will use to open the connection to the above location. ---> <cfset targetUrl = createObject( "java", "java.net.URL" ).init( javaCast( "string", postUrl ) ) /> <!--- Now that we have our URL, let's open a connection to it. This will give us access to the input (download) and output (upload) streams for the target end point. NOTE: This gives us an instance of java.net.URLConnection (or one of its sub-classes). ---> <cfset connection = targetUrl.openConnection() /> <!--- Be default, the connection is only set to gather target content, not to POST it. As such, we have to make sure that we turn on output (upload) before we access the data streams. ---> <cfset connection.setDoOutput( javaCast( "boolean", true ) ) /> <!--- Since we are uploading, we have to set the method to POST. ---> <cfset connection.setRequestMethod( javaCast( "string", "POST" ) ) /> <!--- By default, the connection will locally buffer the data until it is ready to be posted in its entirety. We don't want to hold it all in memory, however; as such, we need to explicitly turn data Chunking on. This will allow the connection to flush data to the target url without having to load it all in memory (this is perfect for when the size of the data is not known ahead of time). ---> <cfset connection.setChunkedStreamingMode( javaCast( "int", 50 ) ) /> <!--- When posting data, the content-type will determine how the target server parses the incoming request. If the target server is ColdFusion, this is especially crtical as it will throw an error if it tries to parse this POST as a collection of name-value pairs. In this case, we WANT it to see the form as multi-part, which will be a collection of name-value pairs. In order to delimit the part of the form post, we need to create a bondary identifier. This is how the server will know where one value ends and the next one starts. This needs to be a random string so as not to show up in the form data itself (as a false boundary). ---> <cfset fieldBoundary = ("POST------------------" & getTickCount()) /> <!--- Set the content type and include the boundary information so the server knowns how to parse the data. ---> <cfset connection.setRequestProperty( javaCast( "string", "Content-Type" ), javaCast( "string", ("multipart/form-data; boundary=" & fieldBoundary) ) ) /> <!--- Now that we have prepared the connection to the target URL, let's get the output stream - this is the UPLOAD stream to which we can write data to be posted to the target server. ---> <cfset uploadStream = connection.getOutputStream() /> <!--- Before we send the file data, we'll send some simple name-value pairs in plain-text format. In order to make it easier to write strings to the upload stream, let's wrap it in a Writer. This will allow us to write string data rather than just bytes. ---> <cfset uploadWriter = createObject( "java", "java.io.OutputStreamWriter" ).init( uploadStream ) /> <!--- Form data makes heavy use of the Carriage Return and New Line characters to delimite values. ---> <cfset crnl = (chr( 13 ) & chr( 10 )) /> <!--- A double break is also used. ---> <cfset crnl2 = (crnl & crnl) /> <!--- Delimit the field. ---> <cfset uploadWriter.write( javaCast( "string", ("--" & fieldBoundary & crnl) ) ) /> <!--- Send the title. ---> <cfset uploadWriter.write( javaCast( "string", ( "Content-Disposition: form-data; name=""title""" & crnl2 & "The Bride" & crnl )) ) /> <!--- Delimit the field. ---> <cfset uploadWriter.write( javaCast( "string", ("--" & fieldBoundary & crnl) ) ) /> <!--- Send the author. ---> <cfset uploadWriter.write( javaCast( "string", ( "Content-Disposition: form-data; name=""author""" & crnl2 & "Julie Garwood" & crnl )) ) /> <!--- Delimit the field. ---> <cfset uploadWriter.write( javaCast( "string", ("--" & fieldBoundary & crnl) ) ) /> <!--- Send the publisher. ---> <cfset uploadWriter.write( javaCast( "string", ( "Content-Disposition: form-data; name=""publisher""" & crnl2 & "Pocket Star" & crnl )) ) /> <!--- Now that we've written the simple name/value pairs, let's post the actual file data as part of the incoming request. This works very much in the same way, although we are going to stream the local file into the post data. Let's open a connection to a local file that we will stream to the output a byte at a time. NOTE: There are more effficient, buffered ways to read a file into memory; however, this is just trying to keep it simple. ---> <cfset fileInputStream = createObject( "java", "java.io.FileInputStream" ).init( javaCast( "string", expandPath( "./data2.txt" ) ) ) /> <!--- Delimit the field. ---> <cfset uploadWriter.write( javaCast( "string", ("--" & fieldBoundary & crnl) ) ) /> <!--- Send the file along. ---> <cfset uploadWriter.write( javaCast( "string", ( "Content-Disposition: form-data; name=""text""; filename=""the_bride.txt""" & crnl & "Content-Type: ""text/plain""" & crnl2 )) ) /> <!--- Read the first byte from the file. ---> <cfset nextByte = fileInputStream.read() /> <!--- Keep reading from the file, one byte at a time, until we hit (-1) - the End of File marker for the input stream. ---> <cfloop condition="(nextByte neq -1)"> <!--- Write this byte to the output (UPLOAD) stream. ---> <cfset uploadWriter.write( javaCast( "int", nextByte ) ) /> <!--- Read the next byte from the file. ---> <cfset nextByte = fileInputStream.read() /> </cfloop> <!--- Add the new line to the field value. ---> <cfset uploadWriter.write( javaCast( "string", crnl ) ) /> <!--- Delimit the end of the post. Notice that the last delimiter has a trailing double-slash after it. ---> <cfset uploadWriter.write( javaCast( "string", (crnl & "--" & fieldBoundary & "--" & crnl) ) ) /> <!--- Now that we're done streaming the file, close the stream. ---> <cfset uploadWriter.close() /> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <!--- At this point, we have completed the UPLOAD portion of the request. We could be done; or we could look at the input (download) portion of the request in order to view the response or the error. ---> <cfoutput> Response: #connection.getResponseCode()# - #connection.getResponseMessage()#<br /> <br /> </cfoutput> <!--- The input stream is mutually exclusive with the error stream, although both can return data. As such, let's try to access the input stream... and then use the error stream if there is a problem. ---> <cftry> <!--- Try for the input stream. ---> <cfset downloadStream = connection.getInputStream() /> <!--- If the input stream is not available (ie. the server returned an error response), then we'll have to use the error output as the response stream. ---> <cfcatch> <!--- Use the error stream as the download. ---> <cfset downloadStream = connection.getErrorStream() /> </cfcatch> </cftry> <!--- At this point, we have either the natural download or the error download. In either case, we can start reading the output in the same mannor. ---> <cfset responseBuffer = [] /> <!--- Get the first byte. ---> <cfset nextByte = downloadStream.read() /> <!--- Keep reading from the response stream until we run out of bytes (-1). We'll be building up the response buffer a byte at a time and then outputting it as a single value. ---> <cfloop condition="(nextByte neq -1)"> <!--- Add the byte AS CHAR to the response buffer. ---> <cfset arrayAppend( responseBuffer, chr( nextByte ) ) /> <!--- Get the next byte. ---> <cfset nextByte = downloadStream.read() /> </cfloop> <!--- Close the response stream. ---> <cfset downloadStream.close() /> <!--- Output the response. ---> <cfoutput> Response: #arrayToList( responseBuffer, "" )# </cfoutput>