<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>blogs.teztech.com &#187; PHP</title>
	<atom:link href="http://blogs.teztech.com/category/php/feed" rel="self" type="application/rss+xml" />
	<link>http://blogs.teztech.com</link>
	<description>Programming, Rock Climbing and Running</description>
	<lastBuildDate>Mon, 27 May 2013 23:47:36 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.1</generator>
	<item>
		<title>Web Service for Document Conversion &#8211; an Odyssey</title>
		<link>http://blogs.teztech.com/2009/06/28/web-service-for-document-conversion-an-odyssey</link>
		<comments>http://blogs.teztech.com/2009/06/28/web-service-for-document-conversion-an-odyssey#comments</comments>
		<pubDate>Sun, 28 Jun 2009 18:02:25 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[T3city]]></category>

		<guid isPermaLink="false">http://teztech.com/?p=28</guid>
		<description><![CDATA[A couple of years ago, I needed a way to convert Microsoft Word documents to Pdf from a C# program. The application I was working on processed hundreds of documents and was run by the system scheduler every day at around 3am, so manual conversion was not an option. I wasn&#8217;t in control of the [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><a href="http://teztech.com/wp-content/uploads/2009/06/convertdemopdf.png"></a><a href="http://teztech.com/wp-content/uploads/2009/06/convertdemo.png"></a>A couple of years ago, I needed a way to convert Microsoft Word documents to Pdf from a C# program. The application I was working on processed hundreds of documents and was run by the system scheduler every day at around 3am, so manual conversion was not an option. I wasn&#8217;t in control of the source documents, so I had to accept the documents the way they were given to me. I needed to do additional processing on the documents, so I wanted to convert them into a universal format. I already had a <a href="http://itextsharp.sourceforge.net/">good library for reading Pdf files</a>.  After researching my options, I settled on using <a href="http://www.openoffice.org/">OpenOffice</a> to do the conversion. OpenOffice has a pretty good Word filter, the ability to create Pdfs and an automation interface accessible to all .Net languages, including C#, so it was a good fit. I know there are commercial solutions and ways to automate Microsoft Office, but the OpenOffice solution was free and fairly easy to use.</p>
<p>Recently, I upgraded my development system from OpenOffice 2.x to OpenOffice 3.1. I can&#8217;t remember now the main reason I upgraded, but I was looking forward to being able to add the ability to convert docx to pdf (Office Open XML support was added in OpenOffice 3.x). I figured the upgrade might require some minor changes to my document conversion code, but it turned out not to be so simple.<br />
<span id="more-28"></span></p>
<p style="text-align: center;"><a href="http://teztech.com/wp-content/uploads/2009/06/convertdemosmall.png"><img class="alignnone size-medium wp-image-31" title="Conversion Server Demo" src="http://teztech.com/wp-content/uploads/2009/06/convertdemosmall-300x196.png" alt="Conversion Server Demo" width="300" height="196" /></a><a href="http://teztech.com/wp-content/uploads/2009/06/convertdemopdf.png"><img class="alignnone size-medium wp-image-30" title="Converted Pdf" src="http://teztech.com/wp-content/uploads/2009/06/convertdemopdf-300x228.png" alt="" width="300" height="228" /></a></p>
<p>The first problem was that OpenOffice no longer showed up under &#8220;Add Reference&#8221; in Visual Studio. After some Google searches, I finally <a href="http://blog.nkadesign.com/2008/net-working-with-openoffice-3/">stumbled across a solution</a>.  OK, wow, digging though install cab files for DLLs, hacking the registry and manipulating the PATH is ugly, but it worked &#8211; at least once. Almost as soon as my test program ran successfully, Visual Studio locked up. The only way to get control back was to kill the Visual Studio process, then restart it. Every time I opened the solution file, after just a few seconds, Visual Studio would lock up again. Removing every trace of the OpenOffice DLL references would prevent the lockups, but without OpenOffice, I could not convert documents. I eventually figured out that the problem was that the library project that used OpenOffice was also included in my web site project. Because the web site uses the library, too, the OpenOffice reference is automatically copied to the web site project. Normally, this would be no big deal &#8211; just a couple of small DLL files in the web site bin folder that would not be used &#8211; but in this case, Visual Studio could not successfully build the web site project as long as the OpenOffice DLLs were referenced.</p>
<p>Now that using OpenOffice was no longer as simple as &#8220;Add Reference&#8221;, it was time to take a look at a way to decouple my application from the OpenOffice DLLs. The API my application needs is pretty simple: Given the bytes of a file in Doc (or .docx) format, return the bytes of the file in Pdf format. Also, there are security risks whenever you open a file in a complicated piece of software like OpenOffice. These two facts pointed me to turning the Pdf conversion into a web service. The client application and web service can be partitioned across computers, so, if I wanted to, I could run the Pdf conversion service on a virtual machine with its own install of OpenOffice and tight security and resource control. The web service API didn&#8217;t need fancy XML: Clients could post the Doc file via a standard HTTP MIME encoded form. The result would be a the PDF byte stream returned via HTTP. This would make the service easy to develop, test and use from clients on many platforms.</p>
<p>I typically develop my large web based applications in C# and Asp.Net. However, for small services like document conversion, I often use PHP. Simple PHP applications can be just a file or two and I can move a PHP based service over to any web server that supports PHP. So, my first attempt at a document conversion service was written in PHP and used the OpenOffice COM API. Using my earlier OpenOffice API code as a guide and some PHP sample code I found on the web, I was able to quickly create a command line PHP application that could perform Pdf conversions. With this proof of concept working, I coded up the PHP web service. But the web service wouldn&#8217;t work. I could see in Task Manager that the OpenOffice executables were launched, but any code running under the web server that tried to access the OpenOffice COM objects would freeze up.</p>
<p>I spent a lot of time trying different security settings for various files and DCOM. I even tried creating a dedicated user and configuring DCOM to run OpenOffice as the dedicated user when the OpenOffice COM objects were activated. I changed Windows event log settings to log all security violations. Nothing would work and I couldn&#8217;t see anything useful in Event Viewer. I tried coding up a simple Asp.Net application and got the same results under the .Net OpenOffice API. Under both PHP/COM and C#/.Net, OpenOffice could be automated by a command line program but would not run under IIS. Right now, I develop on Vista and deploy to Windows Server 2008, so one might think the problem was trying to use IIS 7, but OpenOffice 2 didn&#8217;t have similar problems.</p>
<p>Finally, I gave up trying to run OpenOffice directly under IIS. The problem was either some type of security issue or that OpenOffice was trying to pop up registration dialogs. In Windows, the simple way to run a program under a given user account is to run the program as a Windows Service. It turns out that it is <a href="http://www.codeproject.com/KB/system/WindowsService.aspx">pretty easy to create a Windows Service in C#</a>. The basic idea I came up is was:</p>
<ol>
<li>Install OpenOffice</li>
<li>Create a Windows user account dedicated to Pdf conversion.</li>
<li>Login as the Pdf conversion user and make sure that OpenOffice opens without prompting for registration, crash feedback, etc. (I suspect that the reason OpenOffice 3 won&#8217;t run properly under IIS is that it is prompting the user to register).</li>
<li>Create a Windows Service with a simple API to convert documents</li>
<li>Install the Windows Service and configure it to run as Pdf conversion user</li>
<li>Access the Windows Service from my document conversion Web Service</li>
</ol>
<p>Here is how the API calls flow: <em>Application -&gt; Web Service -&gt; Windows Service</em></p>
<p>As I mentioned earlier, the API between the application and the web service was standard a HTTP Post. Between the Web Service and the Windows Service, I originally planned to use a simple, proprietary TCP/IP protocol. As long as you aren&#8217;t try to get too fancy coding thread pools and asynchronous I/O, at least in C/C++, it&#8217;s pretty easy to open a TCP socket and communicate between client and server with a line oriented protocol (like HTTP, POP3, SMTP, etc.). But, since both the Web Service and the Windows Service were going to run on Windows (at least for now), I decided to take a look at .Net Remoting. The .Net remoting solution was easy to code and use so I ended up sticking with it.</p>
<p>Below is some code to show you how everything fits together:</p>
<p>The C# interface <em>IConverter</em> is compiled into a DLL. The DLL is referenced by the client (Web Service) and server (Windows Service).</p>
<p><!-- BEGIN IConverter --></p>
<pre class="csharpcode">
    <span class="kwrd">public</span> <span class="kwrd">class</span> ConverterEndpoints
    {
        <span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">int</span>    Port = 7047;
        <span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">string</span> Uri  = <span class="str">"DocumentToPdf"</span>;
    }

    <span class="kwrd">public</span> <span class="kwrd">interface</span> IConverter
    {
        <span class="kwrd">void</span> Convert(<span class="kwrd">string</span> srcDocument, <span class="kwrd">string</span> destPdf);
    }
</pre>
<p><!-- END IConverter --></p>
<p>The class <em>ConverterEndpoints</em> holds a couple of constants that the client and server use to setup the TCP port and service name. The code below demonstrates how <em>ConverterEndpoints</em> is used in the Windows Service. I&#8217;ve included code for the entire class so you can see how easy it is to program a Windows Service and access the Windows Event Log in C# &#8211; no 3rd party libraries are required.</p>
<p>The original version of this interface passed the files as simple byte[] variables. Theoretically, the files never need to be written to disk (input and output is via HTTP). But, I found that I could easily pass small byte[] variables across .Net remoting but large byte[] variables failed. Since the Web Service and the Windows Service run on the same machine and the file has to be written to disk for OpenOffice to do the conversion, I settled for creating temporary files and passing the names of the files between the Web Service and Windows Service.</p>
<p><!-- BEGIN WindowsService --></p>
<pre class="csharpcode">
   <span class="kwrd">class</span> WindowsService : ServiceBase
    {
        <span class="kwrd">protected</span> EventLog _log;
        <span class="kwrd">protected</span> TcpChannel _channel;

        <span class="kwrd">public</span> WindowsService()
        {
            ServiceName = <span class="str">"Teztech Document Conversion"</span>;
            EventLog.Source = <span class="str">"Teztech Document Conversion"</span>;
            EventLog.Log = <span class="str">"Application"</span>;
            
            <span class="rem">// These Flags set whether or not to handle that specific</span>
            <span class="rem">//  type of event. Set to true if you need it, false otherwise.</span>
            CanHandlePowerEvent = <span class="kwrd">false</span>;
            CanHandleSessionChangeEvent = <span class="kwrd">false</span>;
            CanPauseAndContinue = <span class="kwrd">true</span>;
            CanShutdown = <span class="kwrd">true</span>;
            CanStop = <span class="kwrd">true</span>;

            <span class="kwrd">if</span> (!EventLog.SourceExists(<span class="str">"Teztech Document Conversion"</span>))
                EventLog.CreateEventSource(<span class="str">"Teztech Document Conversion"</span>, <span class="str">"Application"</span>);

            _log = <span class="kwrd">new</span> EventLog();
            _log.Source = <span class="str">"Teztech Document Conversion"</span>;

            _channel = <span class="kwrd">new</span> TcpChannel(ConvertInterface.ConverterEndpoints.Port);
            ChannelServices.RegisterChannel(_channel, <span class="kwrd">false</span>);

            RemotingConfiguration.RegisterWellKnownServiceType(<span class="kwrd">typeof</span>(DocumentToPdf), ConvertInterface.ConverterEndpoints.Uri, WellKnownObjectMode.Singleton);
        }

        <span class="kwrd">static</span> <span class="kwrd">void</span> Main()
        {
            ServiceBase.Run(<span class="kwrd">new</span> WindowsService());
        }

        <span class="rem">/// &lt;summary&gt;</span>
        <span class="rem">/// Dispose of objects that need it here.</span>
        <span class="rem">/// &lt;/summary&gt;</span>
        <span class="rem">/// &lt;param name="disposing"&gt;Whether or not disposing is going on.&lt;/param&gt;</span>
        <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> Dispose(<span class="kwrd">bool</span> disposing)
        {
            <span class="kwrd">base</span>.Dispose(disposing);
        }

        <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnStart(<span class="kwrd">string</span>[] args)
        {
            _log.WriteEntry(<span class="str">"Service Starting"</span>);

            _channel.StartListening(<span class="kwrd">null</span>);

            <span class="kwrd">base</span>.OnStart(args);

            _log.WriteEntry(<span class="str">"Service Running"</span>);
        }

        <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnStop()
        {
            _log.WriteEntry(<span class="str">"Service Stopping"</span>);

            _channel.StopListening(<span class="kwrd">null</span>);

            <span class="kwrd">base</span>.OnStop();
        }

        <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnPause()
        {
            _channel.StopListening(<span class="kwrd">null</span>);
            <span class="kwrd">base</span>.OnPause();
        }

        <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnContinue()
        {
            _channel.StartListening(<span class="kwrd">null</span>);
            <span class="kwrd">base</span>.OnContinue();
        }
    }
</pre>
<p><!-- END WindowsService --></p>
<p>Below is the code behind file for the Web Service client. As you can see, .Net makes this type of Web Service easy to create.</p>
<p><!-- BEGIN PdfCodeBehind --></p>
<pre class="csharpcode">
<span class="kwrd">public</span> <span class="kwrd">partial</span> <span class="kwrd">class</span> Pdf : System.Web.UI.Page
{
    <span class="kwrd">protected</span> TcpChannel _channel;

    <span class="kwrd">protected</span> <span class="kwrd">void</span> Page_Load(<span class="kwrd">object</span> sender, EventArgs e)
    {
        <span class="kwrd">try</span>
        {
            <span class="kwrd">if</span> (Request[<span class="str">"Username"</span>] == <span class="kwrd">null</span> || Request[<span class="str">"Password"</span>] == <span class="kwrd">null</span>)
                <span class="kwrd">throw</span> <span class="kwrd">new</span> ApplicationException(<span class="str">"Username and Password are required"</span>);

            HttpPostedFile postedFile = Request.Files[<span class="str">"InputFile"</span>];
            <span class="kwrd">if</span> (postedFile == <span class="kwrd">null</span> || postedFile.FileName == <span class="kwrd">null</span> || postedFile.FileName == <span class="str">""</span>)
                <span class="kwrd">throw</span> <span class="kwrd">new</span> ApplicationException(<span class="str">"InputFile was not supplied or file name is empty."</span>);

            <span class="kwrd">string</span> inputFileExtension = Path.GetExtension(postedFile.FileName);
            <span class="kwrd">if</span> (inputFileExtension.Length &lt; 2)
                <span class="kwrd">throw</span> <span class="kwrd">new</span> ApplicationException(<span class="str">"InputFile does not have a valid file name extension."</span>);
            inputFileExtension = inputFileExtension.Substring(1);

            <span class="kwrd">string</span> outputFileName = Request[<span class="str">"OutputFileName"</span>];
            <span class="kwrd">if</span> (outputFileName == <span class="kwrd">null</span> || outputFileName == <span class="str">""</span>)
                outputFileName = Path.GetFileNameWithoutExtension(postedFile.FileName) + <span class="str">".pdf"</span>;

            DbRequest.CheckAuthorization(Request[<span class="str">"Username"</span>], Request[<span class="str">"Password"</span>]);

            <span class="kwrd">if</span> (_channel == <span class="kwrd">null</span>)
            {
                Dictionary&lt;<span class="kwrd">string</span>, <span class="kwrd">string</span>&gt; channelProperties = <span class="kwrd">new</span> Dictionary&lt;<span class="kwrd">string</span>,<span class="kwrd">string</span>&gt;();
                channelProperties[<span class="str">"name"</span>] = <span class="str">""</span>;
                _channel = <span class="kwrd">new</span> TcpChannel(channelProperties, <span class="kwrd">null</span>, <span class="kwrd">null</span>);
                ChannelServices.RegisterChannel(_channel, <span class="kwrd">false</span>);
            }

            <span class="kwrd">string</span> url = <span class="kwrd">string</span>.Format(<span class="str">"tcp://localhost:{0}/{1}"</span>, ConvertInterface.ConverterEndpoints.Port, ConvertInterface.ConverterEndpoints.Uri);

            ConvertInterface.IConverter converter = (ConvertInterface.IConverter)Activator.GetObject(<span class="kwrd">typeof</span>(ConvertInterface.IConverter), url);

            <span class="kwrd">string</span> path = MapPath(<span class="str">"."</span>);
            path = Path.Combine(Path.GetDirectoryName(path), <span class="str">"Temp"</span>);

            <span class="kwrd">using</span> (TempFileCollection tempFiles = <span class="kwrd">new</span> TempFileCollection(path))
            {
                <span class="kwrd">string</span> inputFile  = tempFiles.AddExtension(inputFileExtension);
                <span class="kwrd">string</span> outputFile = tempFiles.AddExtension(<span class="str">"pdf"</span>);

                postedFile.SaveAs(inputFile);
                converter.Convert(inputFile, outputFile);

                Response.Clear();
                Response.ContentType = <span class="str">"application/pdf"</span>;
                Response.AddHeader(<span class="str">"content-disposition"</span>, <span class="kwrd">string</span>.Format(<span class="str">"inline; filename={0}.pdf"</span>, <span class="str">"sample"</span>));

                <span class="kwrd">byte</span>[] pdfBytes = File.ReadAllBytes(outputFile);

                Response.OutputStream.Write(pdfBytes, 0, pdfBytes.Length);
            }
        }        
        <span class="kwrd">catch</span> (Exception ex)
        {
            Response.Clear();
            Response.Write(<span class="kwrd">string</span>.Format(<span class="str">"&lt;HTML&gt;&lt;HEAD&gt;&lt;/HEAD&gt;&lt;BODY&gt;&lt;H1&gt;Conversion Error&lt;/H1&gt;&lt;P&gt;{0}&lt;/P&gt;&lt;/BODY&gt;&lt;/HTML&gt;"</span>, ex.Message));
            Response.StatusCode = 500;
            Response.StatusDescription = ex.Message;
        }
    }
</pre>
<p><!-- END PdfCodeBehind --></p>
<p>For completeness, I have included the code for the actual Web Service&#8217;s .aspx file below. As you can see, it&#8217;s just standard .aspx. boilerplate &#8211; all HTTP output is generated in the code behind file&#8217;s Page_Load event (which will be invoked when the Web Service client posts to the page). This is quick and dirty, but it works.</p>
<p><!-- BEGIN Pdf.aspx --></p>
<pre class="csharpcode">
<span class="asp">&lt;%@ Page Language="C#" AutoEventWireup="true" CodeFile="Pdf.aspx.cs" Inherits="Pdf" EnableViewState="false" %&gt;</span>

<span class="kwrd">&lt;!</span><span class="html">DOCTYPE</span> <span class="attr">html</span> <span class="attr">PUBLIC</span> <span class="kwrd">"-//W3C//DTD XHTML 1.0 Transitional//EN"</span> <span class="kwrd">"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"</span><span class="kwrd">&gt;</span>

<span class="kwrd">&lt;</span><span class="html">html</span> <span class="attr">xmlns</span><span class="kwrd">="http://www.w3.org/1999/xhtml"</span> <span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">head</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">title</span><span class="kwrd">&gt;</span>Untitled Page<span class="kwrd">&lt;/</span><span class="html">title</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">head</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">body</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">form</span> <span class="attr">id</span><span class="kwrd">="form1"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">div</span><span class="kwrd">&gt;</span>
    
    <span class="kwrd">&lt;/</span><span class="html">div</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">form</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">body</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">html</span><span class="kwrd">&gt;</span>
</pre>
<p><!-- END Pdf.aspx --></p>
<p>Here And here is the code for a little PHP application I used to test my conversion Web Service:</p>
<p><!-- BEGIN demo.php --></p>
<pre class="csharpcode">
<span class="kwrd">&lt;?</span><span class="html">php</span> $<span class="attr">ErrorMessage</span> = <span class="kwrd">''</span>; ?<span class="kwrd">&gt;</span>

<span class="kwrd">&lt;!</span><span class="html">DOCTYPE</span> <span class="attr">html</span> <span class="attr">PUBLIC</span> <span class="kwrd">"-//W3C//DTD XHTML 1.0 Transitional//EN"</span> <span class="kwrd">"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">html</span> <span class="attr">xmlns</span><span class="kwrd">="http://www.w3.org/1999/xhtml"</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">head</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">title</span><span class="kwrd">&gt;</span>
File Conversion Server Demo
<span class="kwrd">&lt;/</span><span class="html">title</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">head</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">body</span><span class="kwrd">&gt;</span>

<span class="kwrd">&lt;</span><span class="html">h1</span><span class="kwrd">&gt;</span>File Conversion Server Demo<span class="kwrd">&lt;/</span><span class="html">h1</span><span class="kwrd">&gt;</span>

<span class="kwrd">&lt;</span><span class="html">form</span> <span class="attr">name</span><span class="kwrd">="Demo"</span> <span class="attr">method</span><span class="kwrd">="POST"</span> <span class="attr">enctype</span><span class="kwrd">="multipart/form-data"</span> <span class="attr">action</span><span class="kwrd">="/demo.pdf"</span><span class="kwrd">&gt;</span>

    <span class="kwrd">&lt;?</span><span class="html">php</span> <span class="attr">if</span>($<span class="attr">ErrorMessage</span>)<span class="attr">:</span> ?<span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">p</span><span class="kwrd">&gt;&lt;</span><span class="html">font</span> <span class="attr">size</span><span class="kwrd">="5"</span> <span class="attr">color</span><span class="kwrd">="#008000"</span><span class="kwrd">&gt;&lt;?</span><span class="html">php</span> <span class="attr">echo</span> $<span class="attr">ErrorMessage</span>; ?<span class="kwrd">&gt;&lt;/</span><span class="html">font</span><span class="kwrd">&gt;&lt;/</span><span class="html">p</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;?</span><span class="html">php</span> <span class="attr">endif</span>; ?<span class="kwrd">&gt;</span>
    
    <span class="kwrd">&lt;</span><span class="html">table</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">tr</span><span class="kwrd">&gt;</span>
            <span class="kwrd">&lt;</span><span class="html">td</span><span class="kwrd">&gt;&lt;</span><span class="html">b</span><span class="kwrd">&gt;</span>Username:<span class="kwrd">&lt;/</span><span class="html">b</span><span class="kwrd">&gt;&lt;/</span><span class="html">td</span><span class="kwrd">&gt;</span>
            <span class="kwrd">&lt;</span><span class="html">td</span><span class="kwrd">&gt;&lt;</span><span class="html">input</span> <span class="attr">name</span><span class="kwrd">="Username"</span> <span class="attr">value</span><span class="kwrd">=""</span><span class="kwrd">&gt;&lt;/</span><span class="html">input</span><span class="kwrd">&gt;&lt;/</span><span class="html">td</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;/</span><span class="html">tr</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">tr</span><span class="kwrd">&gt;</span>
            <span class="kwrd">&lt;</span><span class="html">td</span><span class="kwrd">&gt;&lt;</span><span class="html">b</span><span class="kwrd">&gt;</span>Password:<span class="kwrd">&lt;/</span><span class="html">b</span><span class="kwrd">&gt;&lt;/</span><span class="html">td</span><span class="kwrd">&gt;</span>
            <span class="kwrd">&lt;</span><span class="html">td</span><span class="kwrd">&gt;&lt;</span><span class="html">input</span> <span class="attr">name</span><span class="kwrd">="Password"</span> <span class="attr">value</span><span class="kwrd">=""</span><span class="kwrd">&gt;&lt;/</span><span class="html">input</span><span class="kwrd">&gt;&lt;/</span><span class="html">td</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;/</span><span class="html">tr</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">tr</span><span class="kwrd">&gt;</span>
            <span class="kwrd">&lt;</span><span class="html">td</span><span class="kwrd">&gt;&lt;</span><span class="html">b</span><span class="kwrd">&gt;</span>Input File:<span class="kwrd">&lt;/</span><span class="html">b</span><span class="kwrd">&gt;&lt;/</span><span class="html">td</span><span class="kwrd">&gt;</span>
            <span class="kwrd">&lt;</span><span class="html">td</span><span class="kwrd">&gt;&lt;</span><span class="html">input</span> <span class="attr">type</span><span class="kwrd">="file"</span> <span class="attr">name</span><span class="kwrd">="InputFile"</span><span class="kwrd">&gt;&lt;/</span><span class="html">input</span><span class="kwrd">&gt;</span> <span class="kwrd">&lt;/</span><span class="html">td</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;/</span><span class="html">tr</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">table</span><span class="kwrd">&gt;</span>
    
    <span class="kwrd">&lt;</span><span class="html">input</span> <span class="attr">type</span><span class="kwrd">="submit"</span> <span class="attr">value</span><span class="kwrd">="Submit"</span><span class="kwrd">&gt;&lt;/</span><span class="html">input</span><span class="kwrd">&gt;</span> 
    
<span class="kwrd">&lt;/</span><span class="html">html</span><span class="kwrd">&gt;</span>
</pre>
<p><!-- END demo.php --></p>
<p>If you look the PHP form&#8217;s action tag, you&#8217;ll see I used /demo.pdf as the action. Using the very nice <a href="http://learn.iis.net/page.aspx/460/using-url-rewrite-module/">IIS7 URL Rewrite Module</a>, I map all *.pdf URLs to my conversion Web Service. OK, this might be a little overkill, but this way the URLs look nice and all end in .pdf (which is appropriate).</p>
<p>The final piece of the puzzle is a small wrapper class I created to access the conversion Web Service from within my C# application:</p>
<p><!-- BEGIN ConvertClient --></p>
<pre class="csharpcode">
    <span class="kwrd">public</span> <span class="kwrd">class</span> ConvertClient
    {
        <span class="kwrd">public</span> ConvertClient(<span class="kwrd">string</span> convertHost, <span class="kwrd">string</span> username, <span class="kwrd">string</span> password)
        {
            _ConvertHost = convertHost;
            _Username    = username;
            _Password    = password;
        }

        <span class="kwrd">protected</span> <span class="kwrd">string</span> _ConvertHost;
        <span class="kwrd">public</span> <span class="kwrd">string</span> ConvertHost { get { <span class="kwrd">return</span> _ConvertHost; } set { _ConvertHost = <span class="kwrd">value</span>; } }

        <span class="kwrd">protected</span> <span class="kwrd">string</span> _Username;
        <span class="kwrd">public</span> <span class="kwrd">string</span> Username { get { <span class="kwrd">return</span> _Username; } set { _Username = <span class="kwrd">value</span>; } }

        <span class="kwrd">protected</span> <span class="kwrd">string</span> _Password;
        <span class="kwrd">public</span> <span class="kwrd">string</span> Password { get { <span class="kwrd">return</span> _Password; } set { _Password = <span class="kwrd">value</span>; } }

        <span class="kwrd">protected</span> <span class="kwrd">string</span> _Response;
        <span class="kwrd">public</span> <span class="kwrd">string</span> Response { get { <span class="kwrd">return</span> _Response; } set { _Response = <span class="kwrd">value</span>; } }

        <span class="kwrd">protected</span> <span class="kwrd">int</span> _MaxConvertAttempts = 2;
        <span class="kwrd">public</span> <span class="kwrd">int</span> MaxConvertAttempts { get { <span class="kwrd">return</span> _MaxConvertAttempts; } set { _MaxConvertAttempts = <span class="kwrd">value</span>; } }

        <span class="kwrd">protected</span> <span class="kwrd">string</span> _ResponseCode;
        <span class="kwrd">public</span> <span class="kwrd">string</span> ResponseCode { get { <span class="kwrd">return</span> _ResponseCode; } set { _ResponseCode = <span class="kwrd">value</span>; } }

        <span class="kwrd">protected</span> <span class="kwrd">static</span> <span class="kwrd">string</span> _szBoundary    = <span class="str">"SEPARATORSTRINGTEZTECHDOTCOM1"</span>;
        <span class="kwrd">protected</span> <span class="kwrd">static</span> <span class="kwrd">string</span> _szBoundary2   = <span class="str">"\r\n--SEPARATORSTRINGTEZTECHDOTCOM1\r\n"</span>;
        <span class="kwrd">protected</span> <span class="kwrd">static</span> <span class="kwrd">string</span> _szBoundary3   = <span class="str">"\r\n--SEPARATORSTRINGTEZTECHDOTCOM1--"</span>;
        <span class="kwrd">protected</span> <span class="kwrd">static</span> <span class="kwrd">string</span> _szFileSizeHdr = <span class="str">"Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n"</span>;
        <span class="kwrd">protected</span> <span class="kwrd">static</span> <span class="kwrd">string</span> _szFileHdrFmt  = <span class="str">"Content-Disposition: form-data; name=\"InputFile\"; filename=\"{0}\"\r\nContent-Type: application/octet-stream\r\n\r\n"</span>;

        <span class="kwrd">public</span> <span class="kwrd">byte</span>[] ConvertToPdf(<span class="kwrd">byte</span>[] inputFileBytes, <span class="kwrd">string</span> inputFileName, <span class="kwrd">string</span> outputFileName)
        {
            <span class="kwrd">string</span> url = <span class="kwrd">string</span>.Format(<span class="str">"http://{0}/{1}?Username={2}&amp;Password={3}"</span>, ConvertHost, outputFileName, Username, Password);

            <span class="rem">// Calculate upload data size</span>

            <span class="kwrd">string</span> szFileSizeData = <span class="kwrd">string</span>.Format(<span class="str">"{0}"</span>, inputFileBytes.Length + 50000);
            <span class="kwrd">string</span> szFileHdr      = <span class="kwrd">string</span>.Format(_szFileHdrFmt, inputFileName);

            ASCIIEncoding ascii = <span class="kwrd">new</span> ASCIIEncoding(); <span class="rem">// At this time, file names must be ascii</span>
            List&lt;<span class="kwrd">byte</span>&gt; header = <span class="kwrd">new</span> List&lt;<span class="kwrd">byte</span>&gt;(); 
            List&lt;<span class="kwrd">byte</span>&gt; footer = <span class="kwrd">new</span> List&lt;<span class="kwrd">byte</span>&gt;(); 

            header.AddRange(ascii.GetBytes(_szBoundary2));      <span class="rem">// MAX_FILE_SIZE field</span>
            header.AddRange(ascii.GetBytes(_szFileSizeHdr));
            header.AddRange(ascii.GetBytes(szFileSizeData));
            header.AddRange(ascii.GetBytes(_szBoundary2));      <span class="rem">// userfile field</span>
            header.AddRange(ascii.GetBytes(szFileHdr));

            footer.AddRange(ascii.GetBytes(_szBoundary3));
            
            <span class="kwrd">int</span> cbContent = header.Count + inputFileBytes.Length + footer.Count;

            <span class="kwrd">int</span> attempts = 0;

            <span class="kwrd">while</span> (<span class="kwrd">true</span>)
            {
                <span class="kwrd">try</span>
                {
                    attempts++;

                    HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
                    webRequest.Method        = <span class="str">"POST"</span>;
                    webRequest.ContentType   = <span class="kwrd">string</span>.Format(<span class="str">"multipart/form-data; boundary={0}\r\n"</span>, _szBoundary);
                    webRequest.ContentLength = cbContent;

                    <span class="kwrd">using</span> (Stream request = webRequest.GetRequestStream())
                    {
                        request.Write(header.ToArray(), 0, header.Count);
                        request.Write(inputFileBytes, 0, inputFileBytes.Length);
                        request.Write(footer.ToArray(), 0, footer.Count);
                    }

                    <span class="kwrd">using</span> (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
                    {
                        <span class="kwrd">using</span> (BinaryReader reader = <span class="kwrd">new</span> BinaryReader(webResponse.GetResponseStream()))
                        {
                            <span class="kwrd">byte</span>[] pdfBytes = reader.ReadBytes((<span class="kwrd">int</span>)webResponse.ContentLength);
                            <span class="kwrd">return</span> pdfBytes;
                        }
                    }
                }
                <span class="kwrd">catch</span>
                {
                    <span class="kwrd">if</span> (attempts &gt;= MaxConvertAttempts)
                        <span class="kwrd">throw</span>;
                }
            }
        }
    }
</pre>
<p><!-- END ConvertClient --></p>
<p>I didn&#8217;t see an easy way to build a MIME document in .Net, so I&#8217;m building up the MIME document (used in the HTTP Post) from scratch.</p>
<p>To debug the Windows Service, as shown in the code above, I added code to log to the Windows Event Viewer. In this case, I started with working document conversion code, so I didn&#8217;t end up needing to run the service under a debugger. But, one of the nice things about services running as a dedicated process (unlike Apache modules, ISAPI modules and Control Panel applets) is that, with just a simple change (adding a Main function), you can run the service as a regular command line application and attach the Visual Studio debugger to the running process.</p>
<p>To troubleshoot the Web Service, my first step was to disable &#8220;friendly errors&#8221;. With IIS7, you have to do it on <a href="http://mvolo.com/blogs/serverside/archive/2007/07/26/Troubleshoot-IIS7-errors-like-a-pro.aspx">both the web browser client and in IIS7</a>. Debugging is just a matter of attaching the IIS7 process in Visual Studio (as usual for any web application).</p>
<p>All our production Windows servers use the 64 bit edition of Windows 2008 Server. Right now, I happen to do most of my development on a 32 bit edition of Windows Vista. Both platforms run IIS7 and usually, the 32 vs. 64 bit doesn&#8217;t cause any problems. However, the Windows version of OpenOffice only comes in a 32 bit version. This version runs fine on 64 bit Windows, but when programming OpenOffice via .Net, you need a 32 bit application, so you have to <a href="http://stackoverflow.com/questions/41449/i-get-a-an-attempt-was-made-to-load-a-program-with-an-incorrect-format-error-on-a">set the Target Platform in the Project Build Properties to X86</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2009/06/28/web-service-for-document-conversion-an-odyssey/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP IDEs</title>
		<link>http://blogs.teztech.com/2009/03/26/php-ides</link>
		<comments>http://blogs.teztech.com/2009/03/26/php-ides#comments</comments>
		<pubDate>Fri, 27 Mar 2009 03:06:21 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://teztech.com/?p=21</guid>
		<description><![CDATA[What is an Integrated Development Environment (IDE)? For sure &#8220;IDE&#8221; means different things to different programmers. Some probably think an integrated GUI form editor or WYSIWYG HTML editor is critical. Others may think that the most important parts of an IDE are the text editor and code browser. Others still judge IDEs by their code [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>What is an Integrated Development Environment (IDE)? For sure &#8220;IDE&#8221; means different things to different programmers. Some probably think an integrated GUI form editor or WYSIWYG HTML editor is critical. Others may think that the most important parts of an IDE are the text editor and code browser. Others still judge IDEs by their code &#8220;refactoring&#8221; tools.</p>
<p>For me, the IDE goes back to the days of Borland&#8217;s Turbo Pascal and Turbo C. They didn&#8217;t have GUIs much less GUI form editors, HTML wasn&#8217;t yet invented and I don&#8217;t remember the text editor being anything special. What turned them into IDEs was the super efficient work flow&#8230; A typical &#8220;Hello World&#8221; program was mostly a matter of typing in some code and pressing the Run key. If you wanted to debug, you would set your breakpoints right in the editor, then press the Debug key to start your program under the integrated debugger. <span id="more-21"></span>When your program expanded to more than one file, IDEs had simple project facilities that did a good job of bridging the gap between &#8220;Hello World&#8221; and tools for large projects like &#8220;make&#8221; and &#8220;nmake&#8221;. Later, when the GUI APIs got so big that nobody could keep them all their head, integrated documentation became critical to a smooth workflow. For better or worse, I&#8217;ve become addicted to code completion. It doesn&#8217;t work as well for dynamic languages like PHP, but some code completion is better than none.</p>
<p>So, when I think IDE, I think of an all purpose application that makes a programmer&#8217;s day-to-day workflow smooth and efficient&#8230; I find that anything I can do that cuts down my &#8220;cycle&#8221; time improves my productivity (and my mood).</p>
<p>Every since the days of Microsoft C 6, Microsoft has always had the best IDEs for C/C++ and Windows development. Their documentation is better and better integrated, their compilers are way, way faster and the general work flow is always the smoothest. Visual Studio continues this trend for C/C++ and now C# development &#8211; it sure is easy to crank up a new C# web application and start debugging.</p>
<p>As as web host, I end up working on a lot of different sites. These days, there is a lot of PHP code out there and I end up spending a fair amount of time working on PHP projects. For most my really big projects, I typically use C# and Asp.Net, so I have Visual Studio handy. With a few registry tweaks to syntax highly PHP code, Visual Studio works OK as a text editor for PHP, but I wouldn&#8217;t call it a true PHP IDE. For one thing, there is no integrated debugging support. You can kind of get project file support (PHP files can be listed), but the only integrated documentation is for HTML and JavaScript (and sadly, the Microsoft documentation on these frequently referenced topics is IE centric).</p>
<p>So, several times, I&#8217;ve set out on a mission to find a IDE for PHP programming. Last time around, I gave up and stuck it out using Visual Studio as a text editor. After this last round of reviews, I ended up buying NuSphere&#8217;s PhpED.</p>
<p>When you are in the world of &#8220;free&#8221; software, it is tempting to stick only to &#8220;free&#8221; tools. However, in the case of IDEs, I just couldn&#8217;t find a free one that worked well enough to be better than Visual Studio.</p>
<p>One area where PHP needs a lot of work is support for debugging. The basics are there, but having to fiddle with various debugging DLLs is a hassle &#8211; debugging information has been standard for binary programs for years &#8211; how hard could it be to figure out a standard debugging API for IDEs to use?  Having to use query strings to initiate a debugging session is a serious design mistake. With all the rewriting and other things going on, fiddling with URLs just doesn&#8217;t make sense. Visual Studio is able to attach a debugger to server code in just about any situation &#8211; PHP and its IDEs should make this as easy.</p>
<p>The ability to automatically reformatting HTML in both blocks and full pages is very handy&#8230; plenty of tools (like content management systems) make a real mess of HTML. And, for better or worse, you end up using some of these tools over and over so it&#8217;s not like you can fix the code just once and be done with it.</p>
<p>Below are some notes I made as I tried out various PHP IDEs:</p>
<h2>Eclipse PHP</h2>
<p>No cost and open source</p>
<p>Cross-platform (Java)</p>
<p>Attractive</p>
<p>Amazingly slow and memory hungry &#8211; based on Java and requires a Java Runtime to be installed.</p>
<p>Integrated PHP reference uses the php.net web site so, it requires Internet access and is slow.</p>
<p>No integrated CSS, HTML, JavaScript or DOM reference</p>
<p>No WYSIWYG HTML editor, no integrated page preview, either</p>
<p>HTML reformat is weak and not customizable</p>
<p>Go to definition doesn&#8217;t work reliably</p>
<p>Code completion doesn&#8217;t work reliably</p>
<p>Code completion doesn&#8217;t work in CSS files.</p>
<p>HTML code completion uses upper case for tags and attributes.</p>
<p>Subversion integration is slow and shows incorrect status</p>
<p>Crashes frequently</p>
<p>Debugger worked for a simple page, but did not work in a real project</p>
<h2>Komodo IDE</h2>
<p>Attractive </p>
<p>Cross-platform &#8211; interesting choice of Firefox engine</p>
<p>Reasonable performance and memory usage</p>
<p>Subversion integration is quick and accurate</p>
<p>Once configured, PHP syntax checking is very handy</p>
<p>No WYSIWYG HTML editor, but the integrated page preview is well done</p>
<p>Go to definition doesn&#8217;t work reliably</p>
<p>Code completion doesn&#8217;t work reliable (does not seem to follow include paths).</p>
<p>No integrated PHP, CSS, HTML, JavaScript, DOM or MySQL reference</p>
<p>Copy and paste of files in Projects tree fails with no error message</p>
<p>Deluxe syntax PHP + HTML syntax highlighting.</p>
<p>View unsaved changes off File menus is a great idea! The integration of this feature with the recovery of unsaved changes after a crash is nice, too.</p>
<p>Extremely annoying rendering bugs in the text editor</p>
<p>Code completion for CSS and HTML</p>
<p>CSS code completion is not very good. For example, once you type &#8220;margin:&#8221;, you get a drop list of choices instead of a description of the arguments for the margin style.</p>
<p>HTML code completion is annoying because it defaults to upper-case tags (is anyone still NOT using xhtml for new code?)  and does a poor job of suggesting closing tag (typing &#8220;&lt;/&#8221; does not give a drop list of closing tags).</p>
<h2>PHPEdit</h2>
<p>Attractive</p>
<p>Reasonable performance and memory usage</p>
<p>Subversion integration does not work out of the box.</p>
<p>Integrated PHP and MySQL reference is nice, no integrated CSS, HTML, JavaScript or DOM reference.</p>
<p>The ultra-deluxe syntax highlighting changes the highlighting rules depending on where the caret is placed &#8211; very handy for files with integrated HTML, PHP, CSS and JavaScript!.</p>
<p>No code reformat for any language other than PHP.</p>
<p>No code completion for any language other than PHP (CSS and HTML would be nice).</p>
<p>Go to definition (&#8220;Jump to declaration&#8221;) doesn&#8217;t work across files.</p>
<p>Cannot right click to open a file via a simple require() declaration.</p>
<p>No heroics to resolve PHP include paths for code completion.</p>
<p>DB Explorer is an interesting idea, but I think the most important thing about a database to a programmer is the column definitions (names, types, lengths) and, other than seeing the column names in the DB Explorer tree, there is no way see the column definitions for a table. Manually inputing a &#8220;show columns from&#8221; query into the SQL tab didn&#8217;t work &#8211; you end up with an &#8220;Invalid SELECT statement&#8221; error message in a message box.</p>
<p>Non-standard Alt+Tab behavior is not very useful.</p>
<h2>PhpED</h2>
<p>The debugger actually works! But, it&#8217;s a lot more hassle than it should be.</p>
<p>CSS code completion is OK, but Visual Studio is still the best.</p>
<p>PHP code completion is reasonable and follows include paths in some cases.</p>
<p>Integrated documentation for CVS, CSS, HTML, JavaScript, MySQL, PHP, PostgreSQL  and Smarty &#8211; very nice!</p>
<p>The project system works OK for PHP projects, but it is cumbersome and has odd limitations (you can&#8217;t copy and paste files and folders, for example). </p>
<p>Non-standard Alt+Tab behavior is not very useful.</p>
<p>No Subversion integration</p>
<p>No integrated code reformatting (help provides a good explanation of how to integrate 3rd party tools, but these don&#8217;t seem to work well, either).</p>
<p>One of the real selling</p>
<p>The ability to edit files via FTP or SSH file transfer is nice.</p>
<p>Integrated support for SSH terminals is nice (but why, oh why is copy and paste so difficult?)</p>
<h2>Other Suggestions</h2>
<p>A lot of people use Dreamweaver to edit PHP code. I guess if you come from the design world, this makes sense. Syntax highlighting for PHP is pretty poor, but I will say that Dreamweaver has the best support for code reformatting.</p>
<p>Notepad++ has a good syntax highlighting of many languages and there are add-ons to support FTP and integrated spell check.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2009/03/26/php-ides/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Web Page Layouts: Flow (fluid/liquid) vs. Fixed</title>
		<link>http://blogs.teztech.com/2007/08/23/web-page-layouts-flow-fluidliquid-vs-fixed</link>
		<comments>http://blogs.teztech.com/2007/08/23/web-page-layouts-flow-fluidliquid-vs-fixed#comments</comments>
		<pubDate>Thu, 23 Aug 2007 11:23:50 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://teztech.com/2007/08/23/web-page-layouts-flow-fluidliquid-vs-fixed</guid>
		<description><![CDATA[For web site designs, I like a simple, clean look. I also favor a flow (also called fluid or liquid) page layout with no big margins wasting space around the edges. These things make sense to me on a technical basis and I like the aesthetics and usability of these kinds of sites. However, they don&#8217;t [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>For web site designs, I like a simple, clean look. I also favor a flow (also called <a href="http://www.webmasterworld.com/forum83/928.htm">fluid</a> or <a href="http://www.webmasterworld.com/forum21/3450.htm">liquid</a>) page layout with no big margins wasting space around the edges. These things make sense to me on a technical basis and I like the aesthetics and usability of <a href="http://www.inspirationbit.com/spark-your-imagination-with-16-stunning-bits-of-liquid-design/">these kinds of sites</a>. However, they don&#8217;t always lead to the most pleasing look for some tastes.</p>
<p>Here is an article I found interesting: <a href="http://politics.slashdot.org/article.pl?sid=07/02/25/1935215">http://politics.slashdot.org/article.pl?sid=07/02/25/1935215</a></p>
<p>Notice how the high brow, traditional designers favor fixed width layouts with lots of wasted space in the margins. Every single one of these sites is fixed width. Most have top and bottom margins &#8211; a waste of valuable screen real estate, IMO<br />
<span id="more-17"></span></p>
<p>So why do traditional designers favor these kinds of layouts? I&#8217;ve seen <a href="http://www.google.com/search?hl=en&amp;q=fluid+vs+fixed&amp;btnG=Google+Search">lots of arguments</a>, some lame and some worthwhile, but who cares what the designers prefer? Most designers are as much a part of the &#8220;typical audience&#8221; as I am &#8211; no part at all. The job of the web site to please it&#8217;s audience, not it&#8217;s designers. That is easy to say, but how do we really figure out how to best please the &#8220;the audience&#8221;? It&#8217;s something I&#8217;m still working on. Listening carefully to the client is one idea &#8211; often they have spent years working with <em>their </em>clients. Over those years, they have built up instincts about what will and won&#8217;t work. For interactive sites, I&#8217;ve got a stack of books that explain, in painful detail, how to create user interfaces that function well. These books are backed up with actual studies of end user actives. But web sites need to be <em>both</em> functional and pleasing to the eye.</p>
<p>Another related thing I&#8217;ve realized: Designers prefer smallish fonts. I guess, to their tastes, small fonts look better in the layouts. However, over and over again I&#8217;ve found that most end users (well, the ones that actually read the content) prefer larger font sizes. If you ever want to be a hero for a day, find an office with lots of 17&#8243; 1280&#215;1024 LCD monitors and switch everybody from &#8220;Small Fonts&#8221; to &#8220;Large Fonts&#8221; (under the Windows, it&#8217;s under Advanced Display Properties). They&#8217;ll be cheering you as you walk out the door! Overall, I find flow layouts work better with larger font sizes. With a small font size, you can cram lots of stuff into small layout boxes. Flow layouts and larger fonts force you to work out other solutions. Flow layouts also allow you to support the brower&#8217;s ability to change font sizes (<a href="http://developer.yahoo.com/yui/fonts/">YUI fonts</a> has nice support for this). This is a great way to easily accomidate low vision and older users.</p>
<p>Most designers probably don&#8217;t know how to make flow layouts, so they think they are too hard. Once you learn the skills, flow layouts really aren&#8217;t so much harder than the tricks you have to use to brow beat your HTML into a cross-browser fixed layout. They&#8217;ll also make translating your site into other languages a whole lot easier, if you think this might ever occur.</p>
<p>I&#8217;ve also read that fixed layouts have to be done to get content to fit properly into the boxes in the layouts. For busy sites, like the presidential candidate site home pages and sites with paid advertising, the need for a fixed layout makes some sense, but I&#8217;m not sure it&#8217;s something that can&#8217;t be worked around with a different design philosophy.  Advertisers expect their ads to appear in certain locations. However, <a href="http://www.google.com/">Google</a>, <a href="http://slashdot.org">Slashdot</a> and many other sites run on ad revenue have showed us that ads don&#8217;t <em>have </em>to be flash or bitmaps or be fixed at a pixel precise location. &#8220;We have to direct the reader&#8217;s attention&#8221; is a common cry. &#8220;Focus them on the buy button&#8221;, they say, as if a clever graphic design could really fool somebody into buying something. And as if <a href="http://www.amazon.com/">amazon.com</a> has not been showing us how to do this properly in a fluid design for the last 10 years.</p>
<p>Yes, a flow layout won&#8217;t look like a printed brochure, but so what? Only us geezers and designers that lived through the days of printed sales literature find comfort in layouts with portrait style proportions. Look at the proportions of your monitor &#8211; wider than tall, yes? New wide screen TVs and monitors are even wider. My teenage kids spend way more time looking at at the wide, fluid web pages of their favorite interactive web sites than narrow, glossy sales slicks. What do you think they will prefer when they grow up and get jobs (and the money and power to buy things)?</p>
<p>I&#8217;ve heard it said that columns of text need to be kept narrow because &#8220;wide columns of text are hard to read&#8221;. This is just a weak excuse. If I ever find a web page uncomfortable to read because it&#8217;s too wide, I&#8217;ll just make my browser window narrower. Power to the people, I guess. I&#8217;ve heard lame excuses about how users don&#8217;t know how to resize browser windows. Maybe users with 800&#215;600 screens don&#8217;t know how, but flow layouts (and worries about too wide columns of text) aren&#8217;t for them, anyway.</p>
<p>When you design software user interfaces, you have to trade off between first time users and users of intermediate ability. Research shows, for a typical application, there will be far more users of intermediate ability (users that might, for example, know how to resize a window). But the proper audience to design for really depends on the application. For a presidential candidate web site, I could argue either way: Sure, you have the obvious sales brochure aspect of the site, but a <em>good </em>presidential candidate web site will truly be devoted to interactive activities such as volunteer sign up, forums and polls. For a web site that is just a slick brochure with limited interactivity, I can almost grasp the argument that users might not know how to resize their browser windows. Thus, we need to force a width to keep the columns comfortable to read. However, in the particular case of presidential web sites, aren&#8217;t the candidates hoping to draw you into the interactive part of their site? Interactive sites are much better with flow layouts. What type of layout is used behind the home page of your <a href="http://computers.ebay.com/">favorite</a> <a href="http://www.slashdot.org">interactive</a> <a href="http://www.webmasterworld.com/forum21/3450.htm">site</a>?</p>
<p>A final consideration I&#8217;ll leave you with: What happens when your site is viewed on a device like the iPhone (or a Sony PS3, XBox360 or Nintendo Wii)? In the limited space available, it&#8217;s important to make the most of screen real estate. Only a fluid design can accomplish this (without having a site dedicated to each device). My brother has an iPhone. I was interested to find out that he uses it often to do the kind of web surfing that I associate with sitting at a desk. His iPhone lets him web surf in a lot of situations where I would be reading a book or magazine. It&#8217;s interesting to consider my web sites viewed this way. Fluid designs work now and have the benefit of being ready for the future. </p>
<p>When I get a chance, I&#8217;ll post some pictures of flow sites that I&#8217;ve worked on running on the my daughter&#8217;s Wii and my brother&#8217;s iPhone.  They look great ( considering the limited resolution available).</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2007/08/23/web-page-layouts-flow-fluidliquid-vs-fixed/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Simple Tracing and Logging for Asp.Net and .Net Command Line Applications</title>
		<link>http://blogs.teztech.com/2007/08/18/simple-tracing-and-logging-for-aspnet-and-net-command-line-applications</link>
		<comments>http://blogs.teztech.com/2007/08/18/simple-tracing-and-logging-for-aspnet-and-net-command-line-applications#comments</comments>
		<pubDate>Sat, 18 Aug 2007 04:51:38 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://teztech.com/2007/08/18/simple-tracing-and-logging-for-aspnet-and-net-command-line-applications</guid>
		<description><![CDATA[From time to time, I need some quick and dirty logging in my .Net applications.  I don&#8217;t always have time or energy to worry about the ideal logging API. The MSDN documentation and all the examples I found for the built-in .Net logging facilities were confusing and, it turns out, overkill. Logging in Asp.Net Logging [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>From time to time, I need some quick and dirty logging in my .Net applications.  I don&#8217;t always have time or energy to worry about the <a href="http://teztech.com/2007/08/18/logging-for-services-gui-and-command-line-applications">ideal logging API</a>. The MSDN documentation and all the examples I found for the built-in .Net logging facilities were confusing and, it turns out, overkill.</p>
<h2>Logging in Asp.Net</h2>
<p>Logging in Asp.Net is confusing because Microsoft uses the word &#8220;trace&#8221; in two different contexts:<span id="more-23"></span></p>
<p>When a page is rendered and HTML generated, you can have the Asp.Net engine add a bunch of additional page state information to the generated HTML. In your application&#8217;s web.config file, this is the tracing that is controlled by<br />
&lt;trace&gt;&lt;/trace&gt; under &lt;web.config&gt;&lt;/web.config&gt;. In practice, I don&#8217;t often find this kind of tracing helpful, but when I do,  I put the following tags in web.config to make it easy to turn on or off:<br />
<code><br />
&lt;system.web&gt;<br />
  &lt;customErrors mode="Off"/&gt;<br />
  &lt;trace enabled="false" requestLimit="10" pageOutput="true" traceMode="SortByTime" localOnly="true" /&gt;<br />
  &lt;!-- ... --&gt;<br />
&lt;/system.web&gt;</code></p>
<p>With these tag in place, you can enable and disable page state tracing (more like a dump if you ask me) by changing enabled=&#8221;false&#8221; to enabled=&#8221;true&#8221;.</p>
<p>The other use of the word &#8220;trace&#8221; is related to the more traditional form of logging via .Net Debug.Write and Trace.Write. Essentially, under Asp.Net, you use Debug.Write and Trace.Write as usual. Contrary to the MSDN documentation, the default compiler options for Asp.Net web applications allow Debug.Write output in Debug builds and allow Trace.Write output in both Debug and Release builds. If you run your Asp.Net application under the Visual Studio debugger, you will see that Debug and Trace output is redirected to the debug output window without any additional configuration.  As with any use of .Net logging, you can setup tracing to various outputs either in code or via your application&#8217;s configuration file. To configure your Asp.Net web application to log trace output to a file (for use with <a href="http://unxutils.sourceforge.net/">other tools</a>), place the following into your web.config:<br />
<code><br />
&lt;system.diagnostics&gt;  <br />
  &lt;trace autoflush="true"&gt;    <br />
  &lt;listeners&gt;    <br />
  &lt;add name="MyApplicationTracer" type="System.Diagnostics.TextWriterTraceListener, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" initializeData="MyApplicationTrace.log" /&gt;<br />
  &lt;/listeners&gt;<br />
  &lt;/trace&gt;<br />
&lt;/system.diagnostics&gt;</code></p>
<p>This will result in debug (in debug builds) and trace output going into a file named MyApplicaitonTrace.log in your web site&#8217;s root folder.</p>
<h2>Logging in .Net Command Line Applications</h2>
<p>Since .Net applications are easy to create and fairly expressive, I often write small command line utilities that, on other platforms, I might create as shell scripts.  Here are some handy code snips that I use to handle simple logging for these applications:<br />
<code><br />
namespace Program1<br />
{<br />
 ///<br />
<summary></summary>
<p> /// Prepends each output line with the time<br />
 ///<br />
 public class TextWriterTraceListenerWithTime : TextWriterTraceListener<br />
 {<br />
  public TextWriterTraceListenerWithTime()<br />
  : base()<br />
  {<br />
  }</code><code>public TextWriterTraceListenerWithTime(Stream stream)<br />
  : base(stream)<br />
  {<br />
  }</code><code>public TextWriterTraceListenerWithTime(string path)<br />
  : base(path)<br />
  {<br />
  }</code><code>public TextWriterTraceListenerWithTime(TextWriter writer)<br />
  : base(writer)<br />
  {<br />
  }</code><code>public TextWriterTraceListenerWithTime(Stream stream, string name)<br />
  : base(stream, name)<br />
  {<br />
  }</p>
<p>public TextWriterTraceListenerWithTime(string path, string name)<br />
  : base(path, name)<br />
  {<br />
  }</p>
<p>public TextWriterTraceListenerWithTime(TextWriter writer, string name)<br />
  : base(writer, name)<br />
  {<br />
  }</p>
<p>public override void WriteLine(string message)<br />
  {<br />
  base.Write(DateTime.Now.ToString());<br />
  base.Write(" ");<br />
  base.WriteLine(message);<br />
  }<br />
 }</p>
<p>class Program<br />
 {</p>
<p>static void Main(string[] commandLineArgs)<br />
  {<br />
  MemoryStream messageStream = null;<br />
  TextWriterTraceListenerWithTime messageWriter = null;</p>
<p>try<br />
  {<br />
  messageStream = new MemoryStream();<br />
  messageWriter = new TextWriterTraceListenerWithTime(messageStream);<br />
  Trace.Listeners.Add(messageWriter);<br />
  Trace.Listeners.Add(new ConsoleTraceListener());</p>
<p>Trace.WriteLine("This is an example message");<br />
  messageWriter.Flush();</p>
<p>// ...</p>
<p>messageWriter.Flush();<br />
  messageWriter = null;<br />
  }<br />
  catch (Exception e)<br />
  {<br />
  Trace.Write(string.Format("ERROR: A fatal exception occured: {0}: {1}", e.GetType().Name, e.Message));<br />
  if (e.InnerException != null)<br />
  Trace.Write(string.Format(" ({0}: {1})", e.InnerException.GetType().Name, e.InnerException.Message));<br />
  Trace.WriteLine("");</p>
<p>if (messageWriter != null)<br />
  {<br />
  messageWriter.Flush();<br />
  messageStream.Seek(0, SeekOrigin.Begin);<br />
  StreamReader messageReader = new StreamReader(messageStream);<br />
  DbRequest.Singleton.Alert(messageReader.ReadToEnd()); // Send alert message via email<br />
  }<br />
  }<br />
  }<br />
 }<br />
}</p>
<p>Hopefully, you get the idea from the example code above.</p>
<p></code></p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2007/08/18/simple-tracing-and-logging-for-aspnet-and-net-command-line-applications/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Logging for Services, GUI and Command Line Applications</title>
		<link>http://blogs.teztech.com/2007/08/18/logging-for-services-gui-and-command-line-applications</link>
		<comments>http://blogs.teztech.com/2007/08/18/logging-for-services-gui-and-command-line-applications#comments</comments>
		<pubDate>Sat, 18 Aug 2007 04:50:29 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://teztech.com/2007/08/18/logging-for-services-gui-and-command-line-applications</guid>
		<description><![CDATA[This is the first is a series of articles about logging. In this first article, I will describe what I believe is a simple, yet useful logging API. In later articles, I&#8217;ll give some hands on practical advise. As I continue the series, I&#8217;ll add links to the new articles here: Simple Tracing and Logging for Asp.Net and .Net [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>This is the first is a series of articles about logging. In this first article, I will describe what I believe is a simple, yet useful logging API. In later articles, I&#8217;ll give some hands on practical advise. As I continue the series, I&#8217;ll add links to the new articles here:</p>
<ul>
<li><a href="http://teztech.com/2007/08/18/simple-tracing-and-logging-for-aspnet-and-net-command-line-applications">Simple Tracing and Logging for Asp.Net and .Net Command Line Applications</a></li>
</ul>
<p>Wow, some programmers go crazy about logging.  Do a <a href="http://www.google.com/search?hl=en&amp;q=.net+logging">Google search for &#8220;.net logging&#8221;</a> and you&#8217;ll see what I mean. Some of these libraries are huge.  While some applications may really need all the complexity these libraries offer, I have seen it severely abused on more than one occasion. Crank up a copy of <a href="http://teztech.com/2007/02/09/enterprise-overly-complex-and-slow">Blackberry Enterprise Server</a> (BES) and let it run for a week or two. You&#8217;ll find your Windows Event Viewer overflowing with all types of mysterious, confusing and generally useless messages.  In addition to the constant Event Viewer spew, if you let BES run unattended, you&#8217;ll eventually find that your BES hard drive is literally overflowing with many different log files all with obscure names and even more obscure contents.  Exchange Server has a similar problem where some logging is done in Event Viewer, other logging is done to text files. And none of the Exchange Server logging options ever seems to enable the simple email flow messages you need for common day to day administration tasks that I take for granted on our Linux email servers.</p>
<p>Is all this really necessary? <span id="more-22"></span>After 20 years of programming, I&#8217;ve found that the following simple API is more than enough for all my needs:</p>
<p>LogMessage(<em>componentName</em>, <em>severity</em>, <em>format</em>[, <em>formatArg1</em>, <em>formatArg2</em>, ...])</p>
<p>Depending on your programming language and coding standards,  this simple function may need to be wrapped in a class. You might also wrap the function call in a macro to allow for conditional compilation and to keep from having to pass the same <em>componentName</em> over and over.</p>
<p>With this API in mind, you also need a fairly simple configuration system to automatically prefix the messages with time, date, and thread ID and direct messages to some combination of syslog, Windows Event Log, a text file (just 1, please!), console, system debugger, email (for Alerts &#8211; see below)  and user defined functions.</p>
<p>The <em>componentName</em> argument is best handled as a single string determined by the programmer. This keeps things simple, yet gives the programmer a lot of flexibility in both the presentation of messages and the ability to filter messages.</p>
<p>There is no sense in going overboard with adding lots of flexibility for the <em>severity</em> argument. It&#8217;s best to spell things out and keep them simple. You&#8217;re never going to get a group of programmers and system administrators to agree on the difference between &#8220;error&#8221;, &#8220;critical&#8221; and &#8220;alert&#8221;. I use an enumerated type with the following values:</p>
<ul>
<li><strong>Debug</strong> &#8211; Low level messages of interest only to programmers. It should be easy to enable and disable output of debug messages. The only reason a user would enable debug messages is to create a log file to be sent to a programmer. Information messages may be sent to log files and/or UI elements, but they should never be sent to system facilities (such as syslog or the Windows Event Log).</li>
<li><strong>Information</strong> &#8211; Messages that let users know the program is doing what it supposed to do. Information messages are of most value for services where there is no interactive UI to demonstrate that the program is working properly. However, interactive programs can trap Information messages to display status messages during length operations. Information messages may be sent to log files and/or UI elements, but they should never be sent to system facilities.</li>
<li><strong>Warning</strong> &#8211; Messages to let the user know the program encountered a problem, but the problem is not completely unexpected. The program should be able to work around problems that result in warnings (possibly by retrying at a later time).  Warnings should be sent to all log files and system facilities.</li>
<li><strong>Error</strong> &#8211; Messages that need the user&#8217;s attention.  Errors should be sent to all log files and system facilities.</li>
<li><strong>Alert</strong> &#8211; Messages that need the user&#8217;s immediate attention.  The user should be woken from bed. Alerts should be sent to all log files and system facilities.</li>
</ul>
<p>I&#8217;ve found that the most difficult choice is between Error and Alert. For example, at an ISP, email server problems are almost always Alert messages, while for home users or small businesses, email problem can wait until morning. If it&#8217;s not obvious, make everything an Error and assume the administrator will have some way to filter the messages to decide for himself which Errors are really Alerts.</p>
<p>The data type for the <em>format</em> argument depends on your programming language and I8N requirements and tools. For ease of programming, I much prefer to use String.Format style format strings (printf strings in C++ and PHP code).  </p>
<p>I could write an entire article about choosing the best method for formatting strings for output, but quickly:</p>
<p>For production applications, use formatting rather than composition:</p>
<ul>
<li><strong>Do use</strong>: string output = string.Format(&#8220;A = &#8216;{0}&#8217;&#8221;, a)</li>
<li><strong>Do not use</strong>: string output = &#8220;A = &#8216;&#8221; +  a + &#8220;&#8216;&#8221;;</li>
</ul>
<p>As much as I love C++ and <a href="http://www.research.att.com/~bs/C++.html">Bjarne Stroustrup</a> (and understand the arguments for the ability to format user defined types in a safe and type safe manner), favoring formatting over composition leads to programs that are easier to write, easier to read and easier to translate.</p>
<p>If you need to translate your program into other languages, there are two schools of thought for handling literal strings that need to be translated:  </p>
<ul>
<li><strong>In the Windows/Mac style system</strong>, each string is manually assigned a unique integer identifier and all strings are stored in a database of some kind (typically application resources). When the you need a string, you program a call to the database API, passing it the unique integer ID of the string to be retrieved as a lookup key. </li>
<li><strong>In the gettext style system</strong> (UNIX like platforms),  translated versions of the strings are still stored in an external database (though typically in simple text files). However, the original strings created by the programmer (often English) remain in the code. The original strings are used as the lookup key to retrieve the translated string.</li>
</ul>
<p>Kind of like the difference between using raw char* vs. a string class for low level string operations, the Windows style system is the most resource efficient. The gettext system is less tedious to program with and, for better or worse, more tolerant of errors. You can find tools to retrofit your old code for either style. Also, there are various kinds of tools for both systems that allow translators to translate application strings without access to program source code.</p>
<p>Since my API is a lowest common denominator, mapping my simple API to syslog and the Windows Event Log is fairly straightforward.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2007/08/18/logging-for-services-gui-and-command-line-applications/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Switching from PayflowPro to PayPal Website Payments Pro</title>
		<link>http://blogs.teztech.com/2007/03/13/switching-from-payflowpro-to-paypal-website-payments-pro</link>
		<comments>http://blogs.teztech.com/2007/03/13/switching-from-payflowpro-to-paypal-website-payments-pro#comments</comments>
		<pubDate>Wed, 14 Mar 2007 02:32:41 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[T3city]]></category>

		<guid isPermaLink="false">http://teztech.com/2007/03/13/switching-from-payflowpro-to-paypal-website-payments-pro</guid>
		<description><![CDATA[Way back in 1996 I created an e-commerce web site for a small software company I owned. The system allowed customers to purchase and download software our web site. To make the system appealing and easy to use for buyers, we processed credit cards automatically. Back then, there were no Internet credit card processing gateways, but [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>Way back in 1996 I created an e-commerce web site for a small software company I owned. The system allowed customers to purchase and download software our web site. To make the system appealing and easy to use for buyers, we processed credit cards automatically. Back then, there were no Internet credit card processing gateways, but I was able to purchase some rather expensive software that allowed my computer to emulate a credit card terminal. The software used the computer&#8217;s modem to call a modem bank at the credit card processor and complete the transaction. There was a way to automate the software via text files and command lines and that&#8217;s what I did. It was slow, but it worked.</p>
<p>Eventually, my credit card processor teamed up with one of the first companies to offer an Internet credit card processing gateway, Cybercash. I hopped on board as one of the first customers. <span id="more-18"></span>As with a lot of APIs created by hurried teams of inexperienced programmers, Cybercash was much more complex than was necessary to accomplish the simple task at hand. Credit card processing can be really simple: Pass the credit card processor a list of required fields and get back an Boolean success code and an error message.  HTTPS is a fine protocol for this type of stateless transaction that must occur over a secure channel. Input fields can be passed as a URL encoded query string. The error code and error message could be returned an HTTP response code or via a URL encoded fields in the response body. The modern, fancy name for this type of transaction protocol is <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">REST</a>. Under the hood of its overly complex API, Cybercash used the simple REST protocol I described earlier. Liked a lot of other Cybercash programmers, I eventually figured this out on my own and bypassed the complex and cumbersome API to use the simple REST protocol.</p>
<p>Time passed on and Verisign bought up all kinds of important technology related to e-commerce. They purchased Cybercash and started promoting their own gateway API, PayflowPro. Several times, we researched the fees and APIs offered by the various vendors. PayflowPro was the best and <a href="https://www.t3city.com/SupportFAQ.aspx?FAQID=10">the one we used and recommended</a>. The PayflowPro API was a tad bit easier to use than the official Cybercash API &#8211; easy enough that instead of bypassing the API for the REST protocol, I used the actual PayflowPro API function calls.</p>
<p>When the time came to automate the billing for T3city, I started out with PayflowPro. However when it came time to get a new merchant account, I took some time to review my options. On paper, PayPal Website Payments Pro looked pretty good &#8211; they have a nice and simple fee structure (unlike PayflowPro with all sorts of fees billed out at different times) and the fees are competitive if you can get your volume over $3,000/month (and not too bad if you can&#8217;t). The sign-up process was all online. I figured it couldn&#8217;t be too hard to change gateway APIs &#8211; I&#8217;d already used several others &#8211; so I made the switch.</p>
<p>Well, I was wrong about the difficulty level associated with switching from PayflowPro to PayPal. The PayPal API is much more complex. First, there is the fact that the API is <a href="http://en.wikipedia.org/wiki/SOAP">SOAP</a> based. In addition to the more complex SOAP protocol, the PayPal programming examples use another layer on top of the native SOAP API. Why they add this extra layer, I really don&#8217;t know &#8211; maybe it&#8217;s just a fact that every junior programmer feels like an extra layer or two will make things simpler. It doesn&#8217;t make the API any easier to use. In fact, I found the differences between the new layer and the documentation for the base SOAP protocol very confusing. In the end, I elected to ditch the PayPal examples and use PayPalSvc.wsdl and .Net&#8217;s SOAP support tools to generate my own SOAP client for the PayPal API. The resulting API is a little awkward is spots (mostly due to PalPals not so hot API design), but at least it matches the detailed and fairly comprehensive PayPal SOAP protocol documentation.</p>
<p>Once you get over the base complexity of the PayPal SOAP based API, then you have to deal with the stateful nature of PayPal. Credit card processing is normally a simple transaction, but the PayPal Website Payments Pro program requires that you offer your customers the option to pay via PayPal. And, what the heck, allowing payments by PayPal is not such a bad idea.  However, unlike a simple credit card transaction, payments with PayPal naturally flow over a series of pages, some on your site and some on PayPal&#8217;s site. Information such as billing and shipping addresses and invoice line items have to be exchanged in a delicate dance. Assuming you are willing to tackle the complexity of proper error handling, avoiding browser complaints about HTTPS redirects and giving the user the ability to cancel cleanly, getting PayPal transactions to flow smoothly turns out to be a fairly complex.</p>
<p>Is the extra complexity worth it? Consider these points:</p>
<ul>
<li>Both gateways are very reliable. </li>
<li>PayPal fee structure is simpler, clearly specified on their web site and may be less expensive &#8211; YMMV. Traditional store fronts may be able to get better rates from their current processors with a different gateway API.</li>
<li>PayPal charges the same fees for all types of credit cards. AMEX fees are usually higher, so this may save you some money, but, again, YMMV.</li>
<li>PayPal does not require any type of term or volume commitment</li>
<li>Accepting PayPal is nice &#8211; giving customers more ways to pay you is a good thing!</li>
<li>Implementing PayPal Website Payments Pro is a good deal harder than using PayflowPro. Depending on your web site/application, adding stateful credit card / PayPal transaction processing may require adding new database fields, new screens and other large scale programming changes.</li>
<li>Under .Net, PayPal Website Payments Pro does not require any native (non .Net) code. To use PayflowPro in .Net, I load and make native function calls into the native PayflowPro DLL.</li>
<li>PayPal Website Payments Pro requires full names for states (not the typical 2 letter codes) and proper ISO country codes for every transaction. If you don&#8217;t already have validation in place and tables to translate state codes into state names (users <em>will </em>use 2 letter state codes if you let them), this support will require additional programming work.</li>
<li>With PayPal Website Payments Pro, you have to perform an additional step to transfer money from your PayPal account into your bank account.  Funds from credit card purchases show up in your PayPal account immediately, but transfers to your bank account take multiple business days (similar to the delay you&#8217;ll have with a traditional merchant service provider and PayflowPro). In theory, you can use funds in your PayPal account immediately for purchases via PayPal, but you would need a careful strategy to ensure proper accounting.</li>
<li>To credit a customer via PayPal, you must create a new credit transaction that references a special code from the original sale transaction. Unlike PayflowPro, you cannot void a transaction and you cannot change a sale transaction to a credit transaction.</li>
<li>PayPal has different and likely much more strict fraud protection measures. Overall, I like this capability, but it did cause some pain during our switch when credit cards on file from long term customers no longer worked because of mismatched a CVV number, a mistyped expiration date, etc..</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2007/03/13/switching-from-payflowpro-to-paypal-website-payments-pro/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>readme.txt vs. readme.html</title>
		<link>http://blogs.teztech.com/2007/02/16/readmetxt-vs-readmehtml</link>
		<comments>http://blogs.teztech.com/2007/02/16/readmetxt-vs-readmehtml#comments</comments>
		<pubDate>Fri, 16 Feb 2007 22:53:55 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://teztech.com/2007/02/16/readmetxt-vs-readmehtml</guid>
		<description><![CDATA[I&#8217;m mulling over the idea of publishing some of the development tools I&#8217;ve created. For right now, I&#8217;m just looking for a simple way to allow &#8220;friends and family&#8221; to see my notes and source code for a few key projects. I don&#8217;t think many people on my list will have the time and energy to download [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>I&#8217;m mulling over the idea of publishing some of the development tools I&#8217;ve created. For right now, I&#8217;m just looking for a simple way to allow &#8220;friends and family&#8221; to see my notes and source code for a few key projects. I don&#8217;t think many people on my list will have the time and energy to download and compile stuff (unless it&#8217;s something they happen to need right away). I personally like to kick the tires of interesting projects around a bit first by browsing the source code.  So, what I have in mind is a <a href="http://websvn.tigris.org/">web interface to a Subversion source code control repository</a>. The idea of browsing my project folders via the web brings up the idea of using readme.html files. I&#8217;ve considered this idea more than once in the past.<span id="more-16"></span></p>
<p>Most of my projects have a <em>readme.txt</em> file. Typically, a readme file will include an overview of the project and, if it is a code library, a few key source code samples ready to copy and paste. In the past, I have considered using .html files or even .doc files instead of .txt files. I&#8217;ve stuck with readme.txt for two reasons: One, I&#8217;m mostly working in a programmer&#8217;s editor. True, many IDEs have built-in HTML editors, but they are usually slow to load. Two, all the worrying about fonts and styles can be distracting. Overall, both these problems introduce what I&#8217;m going to call <em>friction</em> for lack of a better term. When you&#8217;ve built up some coding momentum (and this is important to do for reasons I may one day get around to writing about), friction slows you down. Some types of friction are important to the overall goals of a project and just can&#8217;t be avoided, but if friction is avoidable, then I say avoid it and gain some coding efficiency. Over time, the overviews and code samples in a readme.txt file can be pretty important to a project.  HTML (or Word) formatting is pretty, but it increases friction for no practical benefit that I can see. Thus, readme.txt instead of readme.html.</p>
<p>So, returning to the subject of browsing a source repository. I started thinking that maybe readme.html was not such a bad idea after all. All you really need, is &lt;h1&gt;, &lt;h2&gt;, &lt;p&gt; and &lt;pre&gt; tags. How much hassle could that be? Tables can be handy, right? So, first I started thinking about getting a global style sheet in place, next I started thinking about creating a template I could use to create a new readme.html.  Then, I started to think about how much friction I was going to add to day-to-day development and what the benefits will be. It&#8217;s a tougher choice than it was in the past, but, for now, I&#8217;m sticking with readme.txt.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2007/02/16/readmetxt-vs-readmehtml/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>&#8220;Green Address Bar&#8221; SSL Certificates</title>
		<link>http://blogs.teztech.com/2007/02/15/green-address-bar-ssl-certificates</link>
		<comments>http://blogs.teztech.com/2007/02/15/green-address-bar-ssl-certificates#comments</comments>
		<pubDate>Fri, 16 Feb 2007 00:02:04 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[Networking]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[T3city]]></category>

		<guid isPermaLink="false">http://teztech.com/2007/02/15/green-address-bar-ssl-certificates</guid>
		<description><![CDATA[I&#8217;ve written other places about SSL certificates. Once upon a time, you bought your SSL certificates from either Verisign or Thawte. Back then, all (both) SSL Certificate Authorities (CAs) did some real validation on the entity (business or person) that was applying for the SSL cert. To validate the entity, they did things like review [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>I&#8217;ve written other places about <a href="http://www.t3city.com/SupportFAQ.aspx?FAQID=9">SSL certificates</a>. Once upon a time, you bought your SSL certificates from either Verisign or Thawte. Back then, all (both) SSL Certificate Authorities (CAs) did some real validation on the entity (business or person) that was applying for the SSL cert. To validate the entity, they did things like review corporate records to make sure addresses matched, looked up phone numbers in public directories and matched drivers licenses to domain registrations.</p>
<p>I can understand why they wanted a bit of money for the work that was required for validation that first year, but overall, SSL certificates have long been overpriced for the value they provide. After that first validation, the next year&#8217;s renewal costs the CA practically nothing, but they used to give no renewal discounts at all and, even now, renewal discounts don&#8217;t exist and multi-year discounts are not as substantial as they could (should?) be.<span id="more-15"></span></p>
<p>Then there were (are) silly SSL cert upgrades that supposedly provided stronger encryption. Well, I suppose those upgrades actually could enhance encryption if you just happened to be running an old, obscure version of IE that was only available outside the US, only for a short time and has not been available since early 2000. Funny how even today, you&#8217;ll find that even the biggest CA charges extra for <a href="http://www.middleware.vt.edu/pubs/ssl.html" title="Even this outdated article suggests that SGC SSL certs are not useful">Server Gated Cryptography</a>, even though no browser modern enough to be secure needs or supports it.</p>
<p>If you want to spend even more on your SSL cert, CAs will happily add on various types of hyper-specific insurance policies and all manner of <a href="http://www.t3city.com/Default.aspx?ArticleID=20">&#8220;site seals&#8221; and  &#8220;trust logos&#8221;</a>.</p>
<p>The entire verification and trust thing is just silly. The percentage of Internet users that would recognize Verisign, Thawte, Comodo or any other CA is vanishingly small. Even if we were to assume your average Internet buyer were a sophisticated, educated, rational consumer, why would they trust some company they&#8217;ve never heard of to tell them how trustworthy amazon.com is? On top of all this, given the year after year rape attempts committed by big CAs with their over pricing of renewals, fake SGC upgrades and other kinds of fake &#8220;strong cryptography&#8221; upgrades, the only thing I personally trust CAs to do is to make as much money as they possibly can with any means at their disposal.</p>
<p>With the useless state of verification and trust, it&#8217;s no wonder that some smaller CAs eventually started verifying less and charging a lot less. Now days, you can buy an SSL certificate that certifies nothing other that you are using SSL. Fair enough &#8211; even that tidbit is more than most consumers are interested in knowing. Practically speaking, SSL is only a technology for the vendor. Vendors should use SSL properly because they care about the consumer enough to not transmit their personal information in the clear over the Internet.  Frankly, if my wife, my Mom or one of my kids finds something they want to buy, as long as the browser don&#8217;t completely refuse to accept the connection, they will be happy to click through all manner of browser warnings to put in a credit card number. Haven&#8217;t we all been trained to ignore these peskey warning messages by now? A few shoppers might be consoled by a a happy, friendly padlock icon, but how many users are fully aware that the pad-lock icon is supposed to be in the browser&#8217;s status bar. &#8220;What&#8217;s a status bar&#8221;, you ask?</p>
<p>Given all this, I&#8217;ve been using the least expensive SSL certs I can find. Here are a couple of different examples &#8211; can you tell what kind of validation was used?</p>
<ul>
<li><a href="https://www.searchenginecommando.com/order/">https://www.searchenginecommando.com/order/</a>  (cheap)</li>
<li><a href="https://www.t3city.com/">https://www.t3city.com/</a> (cheaper)</li>
<li><a href="https://www.embracegroup.com/index2.html">https://www.embracegroup.com/index2.html</a>  (cheapest)</li>
</ul>
<p>I suppose the SSL CAs all got together and decided something just had to be done before everybody started using self-signed SSL certificates. Enter now the &#8220;Green Address Bar&#8221; SSL Certificate. The real name is the &#8220;Extended Validation&#8221;  SSL Certificate, but my name would be better. At least with my name, there is a slight chance that consumers (of SSL certs) will notice.  If you have one of these super-duper certificates, IE&#8217;s address bar is supposed to turn green (as in the color of money, eh?). I think this is a Vista only feature, though.</p>
<p>The SSL vendors want $500 and more for these &#8220;EV&#8221; SSL certs. Personally, I think they are pricing themselves out of the market. Yes, a handful of sites like amazon.com will pay the extra $489 for the SSL cert that makes the browser&#8217;s address bar turn green (as in the color of envy?). I have to guess, though, that the vast majority of SSL certs are purchased for small e-commerce sites like searchenginecommando.com, t3city.com and embracegroup.com. These small operators will rightly assume that public will pay no attention to the fact that the address bar is not green. Personally, I doubt if many people really even look for the SSL lock icon anymore. If EV were just a $50 up-charge, then a lot of small shops might would go ahead and get it (at least for the first year until they find out it didn&#8217;t effect sales). Right now, since the new certs are so expensive, practically nobody will buy them and buyers will just forget all about the green address bar. If they do notice, they&#8217;ll probably just have a vague notion that something wrong with their computer (again). &#8220;Wasn&#8217;t Vista supposed to fix these kinds of problems?&#8221; they&#8217;ll wonder.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2007/02/15/green-address-bar-ssl-certificates/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Linux vs. Windows for Web Hosting</title>
		<link>http://blogs.teztech.com/2007/02/09/linux-vs-windows-for-web-hosting</link>
		<comments>http://blogs.teztech.com/2007/02/09/linux-vs-windows-for-web-hosting#comments</comments>
		<pubDate>Fri, 09 Feb 2007 07:19:06 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[.Net]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[T3city]]></category>

		<guid isPermaLink="false">http://teztech.com/2007/02/09/linux-vs-windows-for-web-hosting</guid>
		<description><![CDATA[A lot of people think I&#8217;m an Linux/open source bigot. That&#8217;s not true at all. I do love Linux and open source. As a programmer, I dig the ability to &#8220;use the source, Luke&#8221;. Not only is looking at source code interesting on its own (at least for some of us), but every now and [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>A lot of people think I&#8217;m an Linux/open source bigot. That&#8217;s not true at all. I do love Linux and open source. As a programmer, I dig the ability to &#8220;use the source, Luke&#8221;. Not only is looking at source code interesting on its own (at least for some of us), but every now and then it really helps with debugging and troubleshooting. Linux Servers are simply better than Windows Servers for a lot of the <a href="http://www.t3city.com">hosting</a> I do, so I learned how to host on Linux. Back in the days of Windows NT, there was no comparison &#8211; our Linux web servers ran heavily loaded for<strong><em> years </em></strong>at a time while Windows NT systems with more than one web site needed regular reboots &#8211; really, I&#8217;m not making it up.<span id="more-14"></span></p>
<p>In modern times, Windows 2000 and Windows 2003 closed the gap for fast and reliable web hosting, but Linux still makes a better email server for ISP type email hosting. The Linux/UNIX security model is simpler than Windows while the Linux process model is slightly more featured. It turns out that these qualities make Linux a good fit for web and email hosting. You really don&#8217;t need complex ACL based permissions for web hosting. The simpler Linux security model works and because it&#8217;s simpler, it is easier to get right.  The Linux lightweight process model (including support for <em>fork</em> with copy on write) is a good match for virtual web hosting &#8211; think about all the perl CGI scripts out there. The lightweight process model is also handy for writing email servers. None of the nice email software I use for hosted email is even available for Windows. It&#8217;s not that it would be ultra-hard to port these packages to Windows. It&#8217;s just painful and really, why bother? DNS hosting is a wash. The only package I like is <a href="http://www.powerdns.com/">PowerDNS</a> and it runs on both Linux and Windows.</p>
<p><a href="http://www.php.net">PHP</a> runs fine on Windows under ISS, but really, why bother? I run Asp.Net on Windows under IIS. It is possible to run .Net applications under <a href="http://www.mono-project.com/">Mono</a>. But, from a practical standpoint, Mono still doesn&#8217;t fully support Asp.Net 2.0 (though it&#8217;s really close now) and Asp.Net 2.0 is so much better than .Net 1.1, I don&#8217;t bother with 1.1 for any of my <a href="http://www.listingstech.com/">own stuff</a>. Overall, running Asp.Net on Linux seems like a lot of struggling against the machine.</p>
<p>When it comes to developer tools, nothing comes close to Microsoft. I can program in vi, emacs or even notepad if I have to, but Microsoft programming tools are better. The IDEs are faster and more integrated. The debuggers are better. The compilers and linkers are faster. My biggest complaint with Linux programming tools is lack of performance. I need a fast compile/link/debug cycle. For anything bigger than a single file project, I can&#8217;t seem to get it on Linux. Last time I had a serious project on Linux, I took the time to try out lots of different IDEs. As I would have guessed, <a href="http://www.kdevelop.org/">Kdevelop </a>was the best &#8211; it&#8217;s beautiful and has tons of features, but I still found it slow and awkward for day-to-day use. I figured out how to use &#8220;<a href="http://www.gnu.org/software/automake/">automake</a> and friends&#8221;. Talk about weird (and slow). I do like <a href="http://subversion.tigris.org/">Subversion</a>. I&#8217;m already using it for all my cross-platform development projects and I&#8217;m thinking about converting everything else over. With Microsoft tools, debugging Asp.Net applications is just like debugging a GUI application. This alone justifies developing all my web application in Asp.Net and hosting them on Windows.</p>
<p>It&#8217;s been more than a couple of years since my last serious desktop GUI application. At that time, Windows and MFC were the obvious platform. If I had to run the application on Linux, I would have tried to get something going with <a href="http://www.winehq.com/site/winelib">Winelib</a>, but MFC support under Winelib has always looked like a PITA. For new development, I would consider using <a href="http://www.trolltech.com/products/qt/">Qt</a> or <a href="http://www.wxwidgets.org/">wxWigits</a>. My big complaint about both these libraries is the deliberate lack of support for exceptions &#8211; talk about stuck in the dark ages. I&#8217;ve done a little bit of WinForms programming. Overall, I think the library is pretty good (and with Mono, it could be cross-platform). My worry is the distribution requirements (though Windows 9X, ME and 2000 are finally starting to fade away) and the <a href="http://teztech.com/2006/10/24/lots-of-ram-for-net-programs">hefty resource requirements</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2007/02/09/linux-vs-windows-for-web-hosting/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP guestbook for abbeyroadontheriver.com</title>
		<link>http://blogs.teztech.com/2006/11/28/php-guestbook-for-abbeyroadontherivercom</link>
		<comments>http://blogs.teztech.com/2006/11/28/php-guestbook-for-abbeyroadontherivercom#comments</comments>
		<pubDate>Tue, 28 Nov 2006 04:20:43 +0000</pubDate>
		<dc:creator><![CDATA[pj]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[T3city]]></category>

		<guid isPermaLink="false">http://teztech.com/2006/11/28/php-guestbook-for-abbeyroadontherivercom</guid>
		<description><![CDATA[When T3city took over the hosting for abbeyroadontheriver.com, they had a guestbook on their web site. Their old host used what appeared to be a custom guestbook written in Cold Fusion. We don&#8217;t support Cold Fusion and I figured I could find something better in PHP, anyway. I found and installed the Purple Yin Guestbook. [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>When <a href="http://www.t3city.com/">T3city</a> took over the hosting for <a href="http://www.abbeyroadontheriver.com/">abbeyroadontheriver.com</a>, they had a guestbook on their web site. Their old host used what appeared to be a custom guestbook written in Cold Fusion. We don&#8217;t support Cold Fusion and I figured I could find something better in PHP, anyway. I found and installed the <a href="http://purpleyin.com/">Purple Yin Guestbook</a>. The PYG script worked well for some time. At some point, though, their guestbook (and guestbooks all over the Internet) got hit by bots (autonomous programs) created by spammers to post in guestbooks. Because the posts were automated, there were many of them. Eventually, abbeyroadontheriver.com&#8217;s owner, Gary, called and we discussed the spam problem. I decided to upgrade to the latest version of PYG. This new version supported <a href="http://www.captcha.net/">captcha</a> images. Gary asked to only use one random digit for the captcha image. This stopped the spam right away. Unfortunately, there were a lot of spam entries to clean up, but Gary worked on this as time allowed.</p>
<p>A couple of weeks ago, Gary started having more problems with his guestbook. It was slow and, more importantly, it wasn&#8217;t taking new posts. After some digging around, I found out that it was slow because PYG stored all the entries in a simple text file. THis is OK (and maybe ideal) for a guestbook with a few posts. Gary&#8217;s guestbook is active and covers several years. Also, there were still a fair number of spam posts that were not visible (Gary&#8217;s guestbook is moderated), but still in the text file. Altogether, Gary has over 3000 posts in his guestbook. <span id="more-8"></span>The combination of simple text file storage with some inefficient coding in PYG and 3000+ posts was resulting in slow page generation. Gary allows HTML in posts. This is mostly so that this guests can post links to pictures, other concerts and videos (on <a href="http://youtube.com/">Youtube</a>, etc.). Allowing HTML can cause problems, but, because his guestbook is moderated, the problems should be correctable. Unfortunately, posts with invalid HTML tripped up the PYG administration interface. So, I started looking for a new guestbook script. PYG doesn&#8217;t have a version that supports MySQL. I wanted something that:</p>
<ul>
<li>Used PHP 4.x</li>
<li>Used MySQL</li>
<li>Used templates to create the Guestbook pages</li>
<li>Allowed the guestbook to be moderated</li>
</ul>
<p>Oddly enough, I couldn&#8217;t find anything that met these objectives. I searched <a href="http://www.google.com/">Google</a> and <a href="http://sf.net/">SourceForge</a>. I came across tons of Guestbook projects. Some required PHP 5. Others didn&#8217;t use templates. I found one that used PHP 4 and templates but didn&#8217;t support moderation. So, I ended up writing one from scratch. I&#8217;ve already got one PHP project on SourceForge, <a href="http://sourceforge.net/projects/phphitcounter/">phpHitCounter</a>. phpGuestbook is already taken, so I&#8217;m thinking phpMysqlGuestbook will be the name. Here are the highlights:</p>
<ul>
<li>Simple configuration (config.php) and installation (edit config.php and run .sql script to create database).</li>
<li>Excellent error messages and error handing for end users ((JavaScript and in page error reporting).</li>
<li>Extra feedback for end users via a special page that explains post moderation.</li>
<li>Fast post display for both end users and administrators</li>
<li>Emails sent to the moderator include a link to the post to be reviewed</li>
<li>Simplified administration</li>
<li>Simple template based customization of HTML output. Templates are valid HTML so they can be maintained in FrontPage, Dreamweaver, etc.</li>
<li>Option to allow HTML content in end user posts. Careful handling of user supplied HTML content in administration interface.</li>
<li>Comprehensive support for banned IPs and banned/moderated content</li>
</ul>
<p>This script is a nice companion to phpHitCounter.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.teztech.com/2006/11/28/php-guestbook-for-abbeyroadontherivercom/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
