Add new documentation
[blerg.git] / www / doc / index.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Blërg Documentation</title>
5 <link rel="stylesheet" href="/css/doc.css">
6 </head>
7 <body>
8
9 <h1>Blërg</h1>
10
11 Blërg is a minimalistic tagged text document database engine that also
12 pretends to be a <a href="/">microblogging system</a>.  It is designed
13 to efficiently store small (&lt; 64K) pieces of text in a way that they
14 can be quickly retrieved by record number or by querying for tags
15 embedded in the text.  Its native interface is HTTP &mdash; Blërg comes
16 as either a standalone HTTP server, or a CGI.  Blërg is written in pure
17 C.
18
19 <ul class="toc">
20   <li><a href="#installing">Installing</a>
21     <ul>
22       <li><a href="#getting_the_source">Getting the source</a></li>
23       <li><a href="#requirements">Requirements</a></li>
24       <li><a href="#configuring">Configuring</a></li>
25       <li><a href="#building">Building</a></li>
26       <li><a href="#installing">Installing</a></li>
27     </ul>
28   </li>
29   <li><a href="#api">API</a>
30     <ul>
31       <li><a href="#api_definitions">API Definitions</a></li>
32       <li><a href="#api_create">/create - create a new user</a></li>
33       <li><a href="#api_login">/login - log in</a></li>
34       <li><a href="#api_logout">/logout - log out</a></li>
35       <li><a href="#api_put">/put - add a new record</a></li>
36       <li><a href="#api_get">/get/(user), /get/(user)/(start record)-(end record) - get records for a user</a></li>
37       <li><a href="#api_info">/info/(user) - Get information about a user</a></li>
38       <li><a href="#api_tag">/tag/(#|H|@)(tagname) - Retrieve records containing tags</a></li>
39       <li><a href="#api_subscribe">/subscribe/(user) - Subscribe to a user's updates</a></li>
40       <li><a href="#api_unsubscribe">/unsubscribe/(user) - Unsubscribe from a user's updates</a></li>
41       <li><a href="#api_feed">/feed - Get updates for subscribed users</a></li>
42       <li><a href="#api_feedinfo">/feedinfo/(user) - Get subscription status for a user</a></li>
43     </ul>
44   </li>
45   <li><a href="#design">Design</a>
46     <ul>
47       <li><a href="#motivation">Motivation</a></li>
48       <li><a href="#web_app_stack">Web App Stack</a></li>
49       <li><a href="#database">Database</a></li>
50       <li><a href="#subscriptions">Subscriptions</a></li>
51       <li><a href="#problems">Problems and Future Work</a></li>
52     </ul>
53   </li>
54 </ul>
55
56 <h2><a name="installing">Installing</a></h2>
57
58 <h3><a name="getting_the_source">Getting the source</a></h3>
59
60 <p>There's no stable release yet, but you can get everything currently
61 running on blerg.dominionofawesome.com by cloning the git repository at
62 http://git.bytex64.net/blerg.git.
63
64 <h3><a name="requirements">Requirements</a></h3>
65
66 <p>Blërg has varying requirements depending on how you want to run it
67 &mdash; as a standalone HTTP server, or as a CGI.  You will need:
68
69 <ul>
70 <li><a href="http://lloyd.github.com/yajl/">yajl</a> &gt;= 1.0.0
71 (yajl is a JSON parser/generator written in C which, by some twisted
72 sense of humor, requires ruby to compile)</li>
73 </ul>
74
75 <p>As a standalone HTTP, server, you will also need:
76
77 <ul>
78 <li><a href="http://www.gnu.org/software/libmicrohttpd/">GNU libmicrohttpd</a> &gt;= 0.9.3</li>
79 </ul>
80
81 <p>Or, as a CGI, you will need:
82
83 <ul>
84 <li><a href="http://www.newbreedsoftware.com/cgi-util/download/">cgi-util</a> &gt;= 2.2.1</li>
85 </ul>
86
87 <h3><a name="configuring">Configuring</a></h3>
88
89 <p>I know I'm gonna get shit for not using an autoconf-based system, but
90 I really didn't want to spend time figuring it out.  You should edit
91 libs.mk and put in the paths where you can find headers and libraries
92 for the above requirements.
93
94 <p>Also, further apologies to BSD folks &mdash; I've probably committed
95 several unconscious Linux-isms.  It would not surprise me if the
96 makefile refuses to work with BSD make, or if it fails to compile even
97 with gmake.  If you have patches or suggestions on how to make Blërg
98 more portable, I'd be happy to hear them.
99
100 <h3><a name="building">Building</a></h3>
101
102 <p>At this point, it should be gravy.  Type 'make' and in a few seconds,
103 you should have <code>blerg.httpd</code>, <code>blerg.cgi</code>,
104 <code>rss.cgi</code>, and <code>blergtool</code>.  Each of those can be
105 made individually as well, if you, for example, don't want to install
106 the prerequisites for <code>blerg.httpd</code> or
107 <code>blerg.cgi</code>.
108
109 <h3><a name="installing">Installing</a></h3>
110
111 <p>While it's not strictly required, Blërg will be easier to set up if
112 you configure it to work from the root of your website.  For this
113 reason, it's better to use a subdomain (i.e., blerg.yoursite.com is
114 easier than yoursite.com/blerg/).  If you do want to put it in a
115 subdirectory, you will have to modify <code>www/js/blerg.js</code> and
116 change baseURL at the top as well as a number of other self-references
117 in that file and <code>www/index.html</code>.  The CGI version should
118 work fine this way, but the HTTP version will require the request to be
119 rewritten, as it expects to be serving from the root.
120
121 <p>You cannot serve the database and client from different domains
122 (i.e., yoursite.com vs othersite.net, or even foo.yoursite.com and
123 bar.yoursite.com).  This is a requirement of the web browser &mdash; the
124 same origin policy will not allow an AJAX request to travel across
125 domains.
126
127 <h4>For the standalone web server:</h4>
128
129 <p>Right now, <code>blerg.httpd</code> doesn't serve any static assets,
130 so you're going to have to put it behind a real webserver like apache,
131 lighttpd, nginx, or similar.  Set the document root to the www
132 directory, then proxy /info, /create, /login, /logout, /get, /tag, and
133 /put to blerg.httpd.  You can change the port <code>blerg.httpd</code>
134 listens on in <code>config.h</code>.
135
136 <h4>For the CGI version:</h4>
137
138 <p>Copy the files in www/ to the root of your web server.  Copy
139 <code>blerg.cgi</code> to your web server.  Included in www-configs/ is
140 a .htaccess file for Apache that will rewrite the URLs.  If you need to
141 call the CGI something other than <code>blerg.cgi</code>, the .htaccess
142 file will need to be modified.
143
144 <h4>The extra RSS CGI</h4>
145
146 <p>There is an optional RSS cgi (<code>rss.cgi</code>) that will serve
147 RSS feeds for users.  Install this like <code>blerg.cgi</code> above.
148
149
150 <h2><a name="api">API</a></h2>
151
152 <p>Blërg's API was designed to be as simple as possible.  Data sent from
153 the client is POSTed with the application/x-www-form-urlencoded
154 encoding, and a successful response is always JSON.  The API endpoints
155 will be described as though the server were serving requests from the
156 root of the wesite.
157
158 <h3><a name="api_definitions">API Definitions</a></h3>
159
160 <p>On failure, all API calls return either a standard HTTP error
161 response, like 404 Not Found if a record or user doesn't exist, or a 200
162 response with a 'JSON failure', which will look like this:
163
164 <p><code>{"status": "failure"}</code>
165
166 <p>Blërg doesn't currently explain <i>why</i> there is a failure, and
167 I'm not sure it ever will.
168
169 <p>On success, you'll either get some JSON relating to your request (for
170 /get, /tag, or /info), or a 'JSON success' response (for /create, /put,
171 /login, or /logout), which looks like this:
172
173 <p><code>{"status": "success"}</code>
174
175 <p>For the CGI backend, you may get a 500 error if something goes wrong.
176 For the HTTP backend, you'll get nothing (since it will have crashed),
177 or maybe a 502 Bad Gateway if you have it behind another web server.
178
179 <p>All usernames must be 32 characters or less.  Usernames must contain
180 only the ASCII characters 0-9, A-Z, a-z, underscore (_), and hyphen (-).
181 Passwords can be at most 64 bytes, and have no limits on characters (but
182 beware: if you have a null in the middle, it will stop checking there
183 because I use <code>strncmp(3)</code> to compare).
184
185 <p>Tags must be 64 characters or less, and can contain only the ASCII
186 characters 0-9, A-Z, a-z, underscore (_), and hyphen (-).
187
188 <h3><a name="api_create">/create</a> - create a new user</a></h3>
189
190 <p>To create a user, POST to /create with <code>username</code> and
191 <code>password</code> parameters for the new user.  The server will
192 respond with JSON failure if the user exists, or if the user can't be
193 created for some other reason.  The server will respond with JSON
194 success if the user is created.
195
196 <h3><a name="api_login">/login</a> - log in</a></h3>
197
198 <p>POST to /login with the <code>username</code> and
199 <code>password</code> parameters for an existing user.  The server will
200 respond with JSON failure if the user does not exist or if the password
201 is incorrect.  On success, the server will respond with JSON success,
202 and will set a cookie named 'auth' that must be sent by the client when
203 accessing restricted API functions (/put and /logout).
204
205 <h3><a name="api_logout">/logout</a> - log out</a></h3>
206
207 <p>POST to /logout with with <code>username</code>, the user to log out,
208 along with the auth cookie in a Cookie header.  The server will respond
209 with JSON failure if the user does not exist or if the auth cookie is
210 bad.  The server will respond with JSON success after the user is
211 successfully logged out.
212
213 <h3><a name="api_put">/put</a> - add a new record</a></h3>
214
215 <p>POST to /put with <code>username</code> and <code>data</code>
216 parameters, and an auth cookie.  The server will respond with JSON
217 failure if the auth cookie is bad, if the user doesn't exist, or if
218 <code>data</code> contains more than 65535 bytes <i>after</i> URL
219 decoding.  The server will respond with JSON success after the record is
220 successfully added.
221
222 <h3><a name="api_get">/get/(user), /get/(user)/(start record)-(end record)</a> - get records for a user</a></h3>
223
224 <p>A GET request to /get/(user), where (user) is the user desired, will
225 return the last 50 records for that user in a list of objects.  The
226 record objects look like this:
227
228 <pre>
229 {
230   "record":"0",
231   "timestamp":1294309438,
232   "data":"eatin a taco on fifth street"
233 }
234 </pre>
235
236 <p><code>record</code> is the record number, <code>timestamp</code> is
237 the UNIX epoch timestamp (i.e., the number of seconds since Jan 1 1970
238 00:00:00 GMT), and <code>data</code> is the content of the record.  The
239 record number is sent as a string because while Blërg supports record
240 numbers up to 2<sup>64</sup> - 1, Javascript uses floating point for all
241 its numbers, and can only support integers without truncation up to
242 2<sup>53</sup>.  This difference is largely academic, but I didn't want
243 this problem to sneak up on anyone who is more insane than I am. :]
244
245 <p>The second form, /get/(user)/(start record)-(end record), retrieves a
246 specific range of records, from (start record) to (end record)
247 inclusive.  You can retrieve at most 100 records this way.  If (end
248 record) - (start record) specifies more than 100 records, or if the
249 range specifies invalid records, or if the end record is before the
250 start record, the server will respond with JSON failure.
251
252 <h3><a name="api_info">/info/(user)</a> - Get information about a user</a></h3>
253
254 <p>A GET request to /info/(user) will return a JSON object with
255 information about the user (currently only the number of records).  The
256 info object looks like this:
257
258 <pre>
259 {
260   "record_count": "544"
261 }
262 </pre>
263
264 <p>Again, the record count is sent as a string for 64-bit safety.
265
266 <h3><a name="api_tag">/tag/(#|H|@)(tagname)</a> - Retrieve records containing tags</a></h3>
267
268 <p>A GET request to this endpoint will return the last 50 records
269 associated with the given tag.  The first character is either # or H for
270 hashtags, or @ for mentions (I call them ref tags).  You should URL
271 encode the # or @, lest some servers complain at you.  The H alias for #
272 was created because Apache helpfully strips the fragment of a URL
273 (everything from the # to the end) before handing it off to the CGI,
274 even if the hash is URL encoded.  The record objects also contain an
275 extra <code>author</code> field, like so:
276
277 <pre>
278 {
279   "author":"Jon",
280   "record":"57",
281   "timestamp":1294555793,
282   "data":"I'm taking #garfield to the vet."
283 }
284 </pre>
285
286 <p>There is currently no support for getting more than 50 tags, but /tag
287 will probably mutate to work like /get.
288
289 <h3><a name="api_subscribe">/subscribe/(user)</a> - Subscribe to a
290 user's updates</a></h3>
291
292 <p>POST to /subscribe/(user) with a <code>username</code> parameter and
293 an auth cookie, where (user) is the user whose updates you wish to
294 subscribe to.  The server will respond with JSON failure if the auth
295 cookie is bad or if the user doesn't exist.  The server will respond
296 with JSON success after the subscription is successfully registered.
297
298 <h3><a name="api_unsubscribe">/unsubscribe/(user)</a> - Unsubscribe from
299 a user's updates</h3>
300
301 <p>Identical to /subscribe, but removes the subscription.
302
303 <h3><a name="api_feed">/feed</a> - Get updates for subscribed users</h3>
304
305 <p>POST to /feed, with a <code>username</code> parameter and an auth
306 cookie.  The server will respond with a JSON list of the last 50 updates
307 from all subscribed users, in reverse chronological order.
308
309 <p>NOTE: subscription notifications are only stored while subscriptions
310 are active.  Any records inserted before or after a subscription is
311 active will not show up in /feed.
312
313 <h3><a name="api_feedinfo">/feedinfo/(user)</a> - Get subscription
314 status for a user</a></h3>
315
316 <p>POST to /feedinfo/(user) with a <code>username</code> parameter and
317 an auth cookie, where (user) is a user whose subscription status you are
318 interested in.  The server will respond with a simple JSON object:
319
320 <pre>
321 {"subscribed":true}
322 </pre>
323
324 <p>The value of "subscribed" will be either true or false depending on
325 the subscription status.
326
327 <h2><a name="design">Design</a></h2>
328
329 <h3><a name="motivation">Motivation</a></h3>
330
331 <p>Blërg was created as the result of a thought experiment: "What if
332 Twitter didn't need thousands of servers? What if its millions of users
333 could be handled by a single highly efficient server?"  This is probably
334 an unreachable goal due to the sheer amount of I/O, but we can certainly
335 try to do better.  Blërg was thus designed as a system with very simple
336 requirements:
337
338 <ol>
339 <li>Store and fetch small chunks of text efficiently</li>
340 <li>Create fast indexes for hash tags and @ mentions</li>
341 <li>Provide a HTTP interface web apps can use</li>
342 </ol>
343
344 <p>And to further simplify, I didn't bother handling deletes, full text
345 search, or more complicated tag searches.  Blërg only does the basics.
346
347 <h3><a name="web_app_stack">Web App Stack</a></h3>
348
349 <table class="pizzapie">
350 <tr><th>Classical model</th></tr>
351 <tr>
352   <td style="background-color: blue; color: white"><b>Client App</b><br>HTML/Javascript</td>
353 </tr>
354 <tr>
355   <td style="background-color: #9F0000; color: white"><b>Webserver</b><br>Apache, lighttpd, nginx, etc.</td>
356 </tr>
357 <tr>
358   <td style="background-color: #009F00; color: white"><b>Server App</b><br>Python, Perl, Ruby, etc.</td>
359 </tr>
360 <tr>
361   <td style="background-color: #404040; color: white"><b>Database</b><br>MySQL, PostgreSQL, MongoDB, CouchDB, etc.</td>
362 </tr>
363 </table>
364
365 <p>Modern web applications have at least a four-layer approach.  You
366 have the client-side browser app, the web server, the server-side
367 application, and the database.  Your data goes through a lot of layers
368 before it actually resides on disk somewhere (or, as they're calling it
369 these days, "The Cloud" *waves hands*).  Each of those layers requires
370 some amount of computing resources, so to increase throughput, we must
371 make the layers more efficient, or reduce the number of layers.
372
373 <table class="pizzapie">
374 <tr><th>Blërg model</th></tr>
375 <tr>
376   <td style="background-color: blue; color: white"><b>Blërg Client App</b><br>HTML/Javascript</td>
377 </tr>
378 <tr>
379   <td style="background-color: #404040; color: white"><b>Blërg Database</b><br>Fuckin' hardcore C and shit</td>
380 </tr>
381 </table>
382
383 <p>Blërg does both by smashing the last two or three layers into one
384 application.  Blërg can be run as either a standalone web server, or as
385 a CGI (FastCGI support is planned, but I just don't care right now).
386 Less waste, more throughput.  As a consequence of this, the entirety of
387 the application logic that the user sees is implemented in the client
388 app in Javascript.  That's why all the URLs have #'s &mdash; the page is
389 loaded once and switched on the fly to show different views, further
390 reducing load on the server.  Even parsing hash tags and URLs are done
391 in client JS.
392
393 <p>The API is simple and pragmatic.  It's not entirely RESTful, but is
394 rather designed to work well with web-based front-ends.  Client data is
395 always POSTed with the usual application/x-www-form-urlencoded encoding,
396 and server data is always returned in JSON format.
397
398 <p>The HTTP interface to the database idea has already been done by <a
399 href="http://couchdb.apache.org/">CouchDB</a>, though I didn't know that
400 until after I wrote Blërg. :)
401
402 <h3><a name="database">Database</a></h3>
403
404 <p>I was impressed by <a
405 href="http://www.varnish-cache.org/">varnish</a>'s design, so I decided
406 early in the design process that I'd try out mmaped I/O.  Each user in
407 Blërg has their own database, which consists of a metdata file, and one
408 or more data and index files.  The data and index files are memory
409 mapped, which hopefully makes things more efficient by letting the OS
410 handle when to read from disk (or maybe not &mdash I haven't benchmarked
411 it).  The index files are preallocated because I believe it's more
412 efficient than writing to it 40 bytes at a time as records are added.
413 The database's limits are reasonable:
414
415 <table class="statistics">
416 <tr><td>maximum record size</td><td>65535 bytes</td></tr>
417 <tr><td>maximum number of records per database</td><td>2<sup>64</sup> - 1 bytes</td></tr>
418 <tr><td>maximum number of tags per record</td><td>1024</td></tr>
419 <table>
420
421 <p>So as not to create grossly huge and unwieldy data files, the
422 database layer splits data and index files into many "segments"
423 containing at most 64K entries each.  Those of you doing some quick math
424 in your heads may note that this could cause a problem on 32-bit
425 machines &mdash; if a full segment contains entries of the maximum
426 length, you'll have to mmap 4GB (32-bit Linux gives each process only
427 3GB of virtual address space).  Right now, 32-bit users should change
428 <code>RECORDS_PER_SEGMENT</code> in <code>config.h</code> to something
429 lower like 32768.  In the future, I might do something smart like not
430 mmaping the whole fracking file.
431
432 <table class="bitstructure">
433 <tr><th>Record Index Structure</th></tr>
434 <tr><td class="B4">offset (32-bit integer)</td></tr>
435 <tr><td class="B2">length (16-bit integer)</td></tr>
436 <tr><td class="B2">flags (16-bit integer)</td></tr>
437 <tr><td class="B4">timestamp (32-bit integer)</td></tr>
438 </table>
439
440 <p>A record is stored by first appending the data to the data file, then
441 writing an entry in the index file containing the offset and length of
442 the data, as well as the timestamp.  Since each index entry is fixed
443 length, we can find the index entry simply by multiplying the record
444 number we want by the size of the index entry.  Upshot: constant-time
445 random-access reads and constant-time writes.  As an added bonus,
446 because we're using append-only files, we get lockless reads.
447
448 <table class="bitstructure">
449 <tr><th>Tag Structure</th></tr>
450 <tr><td class="B32">username (32 bytes)</td></tr>
451 <tr><td class="B8">record number (64-bit integer)</td></tr>
452 </table>
453
454 <p>Tags are handled by a separate set of indices, one per tag.  When a
455 record is added, it is scanned for tags, then entries are appended to
456 each tag index for the tags found.  Each index record simply stores the
457 user and record number.  Tags are searched by opening the tag file,
458 reading the last 50 entries or so, and then reading all the records
459 listed.  Voila, fast tag lookups.
460
461 <p>At this point, you're probably thinking, "Is that it?"  Yep, that's
462 it.  Blërg isn't revolutionary, it's just a system whose requirements
463 were pared down until the implementation could be made dead simple.
464
465 <p>Also, keeping with the style of modern object databases, I haven't
466 implemented any data safety (har har).  Blërg does not sync anything to
467 disk before returning success.  This should make Blërg extremely fast,
468 and totally unreliable in a crash.  But that's the way you want it,
469 right? :]
470
471 <h3><a name="subscriptions">Subscriptions</a></h3>
472
473 <p>When I first started thinking about the idea of subscriptions, I
474 immediately came up with the naïve solution: keep a list of users to
475 which users are subscribed, then when you want to get updates, iterate
476 over the list and find the last entries for each user.  And that would
477 work, but it's kind of costly in terms of disk I/O.  I have to visit
478 each user in the list, retrieve their last few entries, and store them
479 somewhere else to be sorted later.  And worse, that computation has to
480 be done every time a user checks their feed. As the number of users and
481 subscriptions grows, that will become a problem.
482
483 <p>So instead, I thought about it the other way around. Instead of doing
484 all the work when the request is received, Blërg tries to do as much as
485 possible by "pushing" updates to subscribed users.  You can think of it
486 kind of like a mail system.  When a user posts new content, a
487 notification is "sent" out to each of that user's subscribers.  Later,
488 when the subscribers want to see what's new, they simply check their
489 mailbox.  Checking your mailbox is usually a lot more efficient than
490 going around and checking everyone's records yourself, even with the
491 overhead of the "mailman."
492
493 <p>The "mailbox" is a subscription index, which is identical to a tag
494 index, but is a per-user construct.  When a user posts a new record, a
495 subscription index record is written for every subscriber.  It's a
496 similar amount of I/O as the naïve version above, but the important
497 difference is that it's only done once.  Retrieving records for accounts
498 you're subscribed to is then as simple as reading your subscription
499 index and reading the associated records.  This is hopefully less I/O
500 than the naïve version, since you're reading, at most, as many accounts
501 as you have records in the last N entries of your subscription index,
502 instead of all of them.  And as an added bonus, since subscription index
503 records are added as posts are created, the subscription index is
504 automatically sorted by time!  To support this "mail" architecture, we
505 also keep a list of subscribers and subscrib...ees in each account.
506
507 <h3><a name="problems">Problems, Caveats, and Future Work</a></h3>
508
509 <p>Blërg probably doesn't actually work like Twitter because I've never
510 actually had a Twitter account.
511
512 <p>I couldn't find a really good fast HTTP server library.
513 Libmicrohttpd is small, but it's focused on embedded applications, so it
514 often eschews speed for small memory footprint.  This is especially
515 apparent when you watch it chew through a POST request 300 bytes at a
516 time even though you've specified a buffer size of 256K.
517 <code>blerg.httpd</code> is still pretty fast this way &mdash; on my
518 2GHz Opteron 246, <a
519 href="http://www.joedog.org/index/siege-home">siege</a> says it serves a
520 690-byte /get request at about 945 transactions per second, average
521 response time 0.05 seconds, with 100 concurrent accesses &mdash; but a
522 fast HTTP server implementation could knock this out of the park.
523
524 <p>Libmicrohttpd is also really difficult to work with.  If you look at
525 the code, <code>http_blerg.c</code> is about 70% longer than
526 <code>cgi_blerg.c</code> simply because of all the iterator hoops I had
527 to jump through to process POST requests.  And if you can believe it, I
528 wrote <code>http_blerg.c</code> first. If I'd done it the other way
529 around, I probably would have given up on libmicrohttpd. :-/
530
531 <p>The data structures written to disk are dependent on the size and
532 endianness of the primitive data types on your architecture and OS.
533 This means that the databases are not portable.  A dump/import tool is
534 probably the easiest way to handle this.
535
536 <p>I do want to make a FastCGI version eventually, and this will
537 probably be a rather simple modification of cgi_blerg.
538
539 <p>Implementing deletes will be... interesting.  There is room in the
540 record index for a 'deleted' flag, but the problem is deleting any tags
541 referenced in the data.  This requires rescanning the record content and
542 putting a 'deleted' flag in the tag indices.  This will not be pretty,
543 so I'm just going to ignore it and hope nobody makes any mistakes. ;]
544
545 <p>Tag indices can grow arbitrarily large, which will cause problems for
546 32-bit machines around the 3GB mark.  Still, that's something like 80
547 million tags, so maybe it's not something to worry about.
548
549 <p>The API currently requires the client to transmit the user's password
550 in the clear.  A digest-based authentication scheme would be better,
551 though for real security, the app should run over HTTPS.
552
553 </body>
554 </html>