Annoyances: vBulletin Version Check

I hate vBulletin. I really, really, really hate vBulletin, and as much as I’d like to pin it on their apparent disdain for K&R coding style (curly braces should, in my opinion, always be on the same line as the if statement to which they are bound) or their awful code in general, I’m afraid I can’t. Mostly, my experiences have been with vBulletin 3 and 4, but while I have no experience with vBulletin 5, I can’t imagine it’s any better.

vBulletin represents everything that is wrong with the PHP development community as a whole. Or rather, everything from the earliest, darkest ages of PHP antiquity that I’d rather just fade away. It’s 2015, and there’s absolutely no reason to continue encouraging people to do stupid, idiotic things when there’s plenty of fantastic documentation out there that would be much better reading for new developers.

Sadly, though, we have to contend with what have rather than what we want.

(Aside: XenForo isn’t much better, but at least they’re using the Zend Framework and provide at least something of a façade that they might actually know what parameterized statements are.)

Today, I wasted about 15-20 minutes on a confounding problem with a vBulletin plugin. The version check URL wasn’t working–at all. Why? Well, there’s the rub, isn’t it? Here’s what I had done: I put an XML file containing the appropriate nonsense for plugin versioning (which would fail XML validation, but that’s besides the point) with nginx sitting in front, pointed vBulletin at the URL, and… nothing.

What the…?

After some fiddly rubbish, I finally ran Wireshark to see what was going on. Lo and behold, it was obviously much more work to host some simple version checking nonsense than to just throw in a static page:

POST /some/path/to/version.xml HTTP/1.0
Host: example.org
User-Agent: vBulletin Product Version Check
Content-Length: 0
 
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 28 Jul 2015 22:27:25 GMT
Content-Type: text/xml
Content-Length: 52
Last-Modified: Tue, 28 Jul 2015 22:21:13 GMT
Connection: close
ETag: "55b80059-34"
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000
Accept-Ranges: bytes
 
<version productid="test_product">1.0.0</version>

Did one of their developers read somewhere that POST is somehow better than GET? Not only did this violate the principle of least surprise (for me at least), but it also flies in the face of HTTP verb standards. GET is generally expected to be used for repeatable requests with no side effects. POST is generally expected to be used for requests that create a resource or otherwise perform tasks that have side effects. A version check should not, in my opinion, have side effects since the only reason for doing such a thing is to check the damn version in the first place.

An even more egregious problem in this particular case is the lack of any post data. Seriously, look! Content-Length: 0. Nothing. Nada. Zero. Why would you do this? Do I have to write some kind of script just to handle a stupid version check that does nothing but look here, examine the number, and say “Oh, hey, guess I better not update. It’s still the same!”

No. Or rather, Hell, no.

I hate to abuse nginx’s config for something of this nature, but since I haven’t finished setting up a distribution framework for these plugins, all I wanted to do was push some static files indicating the current version somewhere. So, after a cursory search, here’s what I stumbled on:

error_page 405 =200 $uri;

If nginx would otherwise return a “405 Method Not Allowed,” we redirect it to the URL we were going to retrieve anyway, and return that. It’s a horrible workaround, and there’s probably better, but I shouldn’t have to do this in the first place.

I suppose I could understand it if you were passing along some sort of token with the version check, perhaps for client identification or similar, but in this case it’s just used to determine whether a plugin has outstanding updates ready. Let’s GET real, people. Stop abusing HTTP verbs.

Oh, and that tag (yes, you know which one) is an inside joke. I’m not allowed to repeat it in mixed company.

No comments.
***

Some Thoughts on Bukkit

I have posted some thoughts on the current Bukkit fiasco. I won’t repeat them here since this blog isn’t intended for personal musings of the sort, but if you aren’t aware of my other blog, you may find the read to be of interest.

No comments.
***

Porting from PyQt to PySide

I recently put together a small project in PyQt, but the license is something that makes me a bit unsettled. I wanted to release the sources under the MIT or BSD licenses, but the requirements of the GPL prohibit carrying out the spirit of either of these, at least in terms of practicality (no downstream commercial use). Thus, I’ve been somewhat torn: I appreciate the GPL, but I more deeply appreciate the MIT/BSD license family for the greater extent of freedom they afford. I disagree that allowing commercial use somehow precludes “freedom” because developers have to eat–and for that matter, I’ll probably purchase a PyQt license at some point in the future, if needed, because it is actively maintained by a small business.

Since starting the project, I rediscovered PySide, an LGPL-licensed alternative to PyQt that acts mostly as a drop-in replacement for the latter. There are some differences that require minor changes to your sources before it can act as a complete replacement, but they’re so minimal as to be of no consequence. Of the ones that I’ve encountered, I’ll document them here since it may not be immediately obvious (although some of the easier cases are).

Signal() and Slot() declarations

PySide’s signal and slot declarations are identical (more or less) to PyQt’s with the exception that they lack a “pyqt” prefix. Thus, the following code:

class MyDialog (QtGui.Dialog):
    progressFinished = QtCore.pyqtSignal()

Must be rewritten as:

class MyDialog (QtGui.Dialog):
    progressFinished = QtCore.Signal()

Likewise, @QtCore.pyqtSlot() decorators are supported by PySide but must be rewritten as @QtCore.Slot(). To this extent, PySide eliminates a handful of unnecessary characters and provides more obvious translation from C++.

setCheckState doesn’t accept integers

PyQt is somewhat more forgiving when passed Python data types and will usually attempt to do the right thing. PySide expects the bindings to follow their C++ cousins more or less exactly, including setting a checkbox’s state via setCheckState(). Thus, checkbox states cannot simply be assigned with the integers 0, 1, or 2 as is possible in PyQt. Instead, the developer must use QtCore.Qt.CheckState.Checked, QtCore.Qt.CheckState.PartiallyChecked, or QtCore.Qt.CheckState.Unchecked. This code does not appear to be compatible between PyQt and PySide.

Python data types may behave unexpectedly in signals

Similar to CheckState, signal arguments are not automatically wrapped (or converted) from Python data types to C++ data types. In one particular case I encountered, larger integers may generate “overflow” errors when exceeding the boundaries of a signed 32-bit integer (2.1 billion, or thereabouts). The only solution in this case is to declare the signal arguments as a Python object as per this example. Other incompatibilities may exist in signal declarations, but this is the one that I encountered fairly early on. Lists and dictionaries are correctly wrapped and behave as expected.

Most other behaviors (threads included) are identical between PyQt and PySide, which is useful for more complicated applications that require offloading activities to one or more threads and error messages appear similar enough (for the most part). More importantly, PySide is a mature enough alternative to PyQt for most uses, although I’ve yet to try it under Windows. It appears binary-only installation is supported on that platform.

No comments.
***