-
Notifications
You must be signed in to change notification settings - Fork 0
/
look-mom-no-ports.html
130 lines (126 loc) · 9.77 KB
/
look-mom-no-ports.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<!DOCTYPE html>
<html lang="en">
<head>
<title>meandering journey</title>
<meta charset="utf-8" />
<link href="./theme/css/main.css" type="text/css" rel="stylesheet" />
<link href="http://meandering.journey.sk/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="meandering journey Full Atom Feed" />
<link href="http://meandering.journey.sk/feeds/misc.atom.xml" type="application/atom+xml" rel="alternate" title="meandering journey Categories Atom Feed" />
</head>
<body id="index" class="home">
<div class="all">
<div class="extra-info">
<aside>
<h3>About the blog</h3>
A platform to practice my written communication skills.
The range of topics tends to surprise even myself.
</aside>
<aside>
<h3>About the author</h3>
My name is Ján Hušták. I live near
<a href="http://maps.google.com/maps?q=bratislava&z=6">Bratislava</a>.
I've been developing software professionally since 1998.
The Java platform has served me well but I don't dwell on it.
</aside>
<aside>
<h3>Links</h3>
<a href="http://www.journey.sk">Main site</a>,
<a href="http://coding.journey.sk">projects page</a>,
<a href="https://github.com/codingjourney">GitHub</a>.
Sorry, no social networks. I do read mail sent to
coding at journey.sk.
</aside>
<aside id="tags">
<h3>Tags</h3>
<a href="./tag/motivation.html">motivation</a>
- <a href="./tag/htpc.html">HTPC</a>
- <a href="./tag/openbsd.html">OpenBSD</a>
- <a href="./tag/qt.html">Qt</a>
- <a href="./tag/upsheet.html">upsheet</a>
- <a href="./tag/python.html">Python</a>
- <a href="./tag/kde.html">KDE</a>
- <a href="./tag/cloud-computing.html">cloud computing</a>
- <a href="./tag/caldav.html">CalDAV</a>
- <a href="./tag/howto.html">howto</a>
- <a href="./tag/jetty.html">Jetty</a>
- <a href="./tag/craftsmanship.html">craftsmanship</a>
- <a href="./tag/meta.html">meta</a>
- <a href="./tag/music.html">music</a>
- <a href="./tag/it-misadventures.html">IT misadventures</a>
- <a href="./tag/algorithms.html">algorithms</a>
- <a href="./tag/android.html">Android</a>
- <a href="./tag/cups.html">CUPS</a>
- <a href="./tag/security.html">security</a>
- <a href="./tag/html5.html">HTML5</a>
</aside>
<aside class="links">
<h3>Recent articles</h3>
<a href="./much-more-fun-with-planning-poker.html">Much more fun with Planning poker</a>
<a href="./the-child-that-grew-too-fast.html">The child that grew too fast</a>
<a href="./mare-nostrum-at-konzerthaus.html">Mare Nostrum at Konzerthaus</a>
<a href="./too-much-fun-with-planning-poker.html">Too much fun with Planning poker</a>
<a href="./long-time-no-blog.html">Long time no blog</a>
<a href="./what-i-did-last-summer.html">What I did last summer</a>
<a href="./october-2013-is-here.html">October 2013 is here</a>
<a href="./october-2013.html">October 2013</a>
</aside>
</div><!-- /.extra-info -->
<div class="main-column">
<header id="banner" class="body">
<h1><a href=".">meandering<img src="./theme/images/logo.png"/>journey</a></h1>
</header><!-- /#banner -->
<section id="content" class="body">
<header>
<h3>
<a href="look-mom-no-ports.html" rel="bookmark"
title="Permalink to Look mom, no ports!">Look mom, no ports!</a></h2>
</header>
<footer class="post-info">
Published on <abbr class="published" title="2011-02-13T17:04:00"> Sun 13 February 2011 </abbr> under
<a href="./tag/it-misadventures.html">IT misadventures</a>, <a href="./tag/qt.html">Qt</a>, <a href="./tag/jetty.html">Jetty</a> </footer><!-- /.post-info -->
<div class="entry-content">
<p>I'm currently updating a commercial HTML-based desktop application that I wrote a few years ago. Customers keep having permissions trouble on Windows with the back-end opening a TCP port for serving front-end's HTTP requests. I decided to try to refresh both the back-end (to Jetty 7.3.0) and the front-end (to WebKit running in Java via Qt Jambi) and run them in one process with no TCP ports involved. Both components have reputations for being easy to embed, so I thought it shouldn't be too hard. Yeah right...</p>
<p>Actually, things went very smoothly at first. Here's what I discovered:</p>
<ul>
<li>Jetty accepts requests via Connectors; one of them is LocalConnector.</li>
<li>Obviously, setting up Jetty with LocalConnector is beyond the scope of this post :-)</li>
<li>LocalConnector.getResponses() accepts a String argument containing an HTTP request just as you would type it into a telnet window (like "GET /whatever HTTP/1.0" followed by two newlines) and returns an HTTP response, also as a String ("HTTP/200 OK blah blah..."). It's a primitive interface but it does work.</li>
<li>The method mentioned above doesn't work very well if you want to GET binary data such as images (yeah, you could use "Content-Encoding: base64" but come on...). There is, fortunately, a variant of LocalConnector.getResponses() that accepts a ByteArrayBuffer along with a boolean "keepOpen" argument which I simply set to false. The method otherwise works just like the String variant.</li>
</ul>
<p>As for a WebKit front-end, once you have Qt Jambi set up properly (with -Djava.library.path etc.) it's really easy to create a QWebView, point it at a URL and splash it all over your display. To prevent HTTP traffic, however, I needed to intercept requests coming from WebKit and route them to my LocalConnector:</p>
<ul>
<li>The thing to do is QWebView.page().setNetworkAccessManager(magic), where "magic" is a subclass of QNetworkAccessManager that overrides createRequest() to do the necessary, well, magic.</li>
<li>Despite superficial appearances, createRequest() actually accepts QNetworkRequest as a parameter and returns a QNetworkReply. Go figure. Additional parameters for createRequest() include an Operation enum to distinguish GETs from POSTs, and a QIODevice (think InputStream) containing the data of a POST request.</li>
<li>Since QNetworkReply is also a stream-like QIODevice, returning custom content from createRequest() involves writing a QNetworkReply subclass that knows where to look for when clients call its read*() methods. This is not quite easy to do.</li>
</ul>
<p>Anyway. After a few hours I had a web application running in a WebKit window with not a TCP packet in sight.The trouble came when I tried to process a POST request (yes, the application does involve filling out forms). What followed was a bout of frustration, head-scratching and just plain unhappiness that I don't wish upon anyone:</p>
<ul>
<li>First, you have to realize that Qt Jambi (including its WebKit component) is actually a C++ beast in sheep-like Java clothing. No matter how sweet the API is, the implementation is a bitch to debug even if, technically, you have access to the source code.</li>
<li>What happened is that my createRequest() was called with a PostOperation as it should be but the accompanying QIODevice was empty. More specifically, calling canReadLine() on it returned false.</li>
<li>Setting a breakpoint and examining the QIODevice's internals didn't show anything interesting (it was basically a proxy object for a C++ implementation).</li>
<li>I resorted to the oldest tool in a programmer's toolbox: trace statements. Here's what they gave me:<ul>
<li>formData.isOpen(): true</li>
<li>formData.isReadable(): true</li>
<li>formData.isSequential(): true</li>
<li>formData.atEnd(): true</li>
<li>formData.bytesAvailable(): 0</li>
<li>formData.size(): 0</li>
<li>formData.pos(): 0</li>
</ul>
</li>
</ul>
<p>I tried routing regular HTTP post requests through the parent class and it worked even though the request objects looked exactly the same and returned exactly the same trace results! I was getting ready to give my own QNetworkRequest to the parent implementation that would trace all calls made to it but I tried getByte() and... there was data! WTF?! Point: QNetworkRequest was in a weird uninitialized state where it thought it was empty when it really wasn't. It snapped out of it when prodded for data.</p>
<p>Another problem surfaced when I tried to get a Dojo Tree widget going. During initialization, Dojo loads its modules via AJAX and it was failing with the dreaded "NETWORK_ERR: XmlHttpRequest Exception 101". Cost me a lot of blind paths - I tried setting the .js files in different paths relative to the referring .jsp, reading compressed Dojo sources, diagnosing with window.alert() etc. In the end, I understood that WebKit was having some problem with the QNetworkReply I was producing. I made my Content-Length calculation more robust - no change. Finally, I looked at QNetworkReply objects produced by the regular QNetworkAccessManager and made sure all the attributes it fills out were also filled out in my replies. That helped. I know, I should have done it at the start but why did it matter with AJAX requests and not with regular ones? Oh well...</p>
<p>The AJAX issue turned out to be the last major obstacle. My application now runs without opening any TCP ports and both WebKit and Jetty proved reasonably embeddable. I might use this set-up in other contexts as well.</p>
</div><!-- /.entry-content -->
</section>
<footer id="contentinfo" class="body">
<address id="about" class="vcard body">
Proudly powered by <a href="http://getpelican.com/">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</address><!-- /#about -->
</footer><!-- /#contentinfo -->
</div><!-- /.main-column -->
</div><!-- /.all -->
</body>
</html>