mary rose cook

How I made Street Hoarding, a Node.js and Redis application, or, a super simple explanation of asynchronicity, event loops, non-blocking IO, JavaScript, Comet and Node

Second update: thanks to James Coglan again, I have modified the code. Now, the client message requests are held open until there is a new message to return, thus reducing the load on the server.

Update: Thanks to James Coglan for providing some lovely technical corrections to this article.

Go to the Street Hoarding homepage and you will see a message in big letters. If you wish, you can type another message in the text box at the bottom of the page, press return, and see it take the place of the old message. Anyone else on the site at that moment will see your words within a few hundredths of a second. It’s kind of like a community pin-up board, or some hoarding on a building site, or a promiscuous IM client with a very short memory.

Some of the key parts of the code were taken from Ryan Dahl’s demo chat app for Node.js.

My aim with this article is to explain how everything works to someone who is like I was before I wrote Street Hoarding: hazy about asynchronicity, event loops, non-blocking IO, JavaScript and Node.js.

There are two elements: the client and the server.

The client

This is an HTML page that lays out the main message and the text box. It is also the JavaScript that runs on the user’s browser. The JavaScript has two key functions.

longPoll() runs the whole time the user has the webpage open. It takes some data. If this data is null, it is ignored. If it is not null and has a message component, that message is displayed on the webpage through a jQuery update to the message div. Either way, an XMLHttpRequest request is then made with jQuery to the /latest_message url on the server. This request takes some time, but it is asynchronous. That is very important. When longPoll() is run, the data is processed, the URL request is made and, then, the execution of longPoll() continues past the $.ajax() call, and control is passed back to the computer processor so it can carry on doing other work. When a success response comes back, the success function inside the $.ajax() function is run. This pauses for a moment, then calls longPoll() again, passing it the data that the server responded with. Next time through, the message inside this data will be written to the HTML page and the user will see it.

tryToSendMessage() is called when the user submits a new message via the text field on the HTML page. It first sends an (asynchronous, as always) request to the server to ask it whether the message the user entered has ever been said before. If it has, it just tells the user they aren’t being original and finishes. Otherwise, it sends an (asynchronous) request to the /send_message URL, passing the user’s message as a parameter, thus telling the server to save the message.

There are some improvements that could be made to this code. First, when the message is sent to the server, it does not get updated in the user’s browser immediately. That will have to wait until the longPoll() function gets its next response from the server. Second, the user has no idea whether the /send_message request was successful until longPoll() updates the webpage. Third, the message is actually sent twice. The uniqueness check request and send message request could have been combined into a single /send_message request that had the server respond with either an indication of success or a message saying that the message was not unique.

I keep on saying the word asynchronous. Everyone who talks about Node.js goes on about asynchronous execution and this other thing, non-blocking input and output (IO). What is crazy is that we haven’t even got to the Node.js stuff, yet. This is all browser magic that we’ve had for the last whatever years.

So, let’s go back a bit.

When a request goes from the client to the server – either asking for the latest message or sending a new message – the computer processor doesn’t hang around waiting for a response. Instead, it moves on and deals with other tasks. The processor returns its attention to the request when the response comes in. Thus, the input and output are non-blocking. Which is to say, waiting for data to arrive or be sent does not hold the processor up from its other tasks. From this, we get asynchronicity – lines of code can get executed out of order. If there is a pause whilst a function waits for something to happen and that something does not require the computer’s processor, other work can be done in the meantime.

How does this work?

There is this thing called an event loop and every browser has one. This is a function that just goes around and around, taking note of things that happen like a woman alone in a house at night straining to hear every floorboard creak and passerby’s creep. Code, like jQuery, that is running is the browser, can register its interest in different types of event. So, when the $.ajax() jQuery function is called in longPoll(), it sends out the request and then tells the event loop it would be very interested in hearing about any HTTP responses that come back from the server. The event loop eventually gets the response and passes it to jQuery which looks at the response to see if the request was a success, and then runs one of the two functions that we defined in longPoll().

This is where JavaScript plays its part.  JavaScript has – and you may have heard this term before – first class functions. These confer several abilities, but the one we care about is that functions can be passed as arguments.  In longPoll(), functions are passed as the fifth and sixth properties of the $.ajax() call. The first is to be run in the case of a response that indicates an error, the second in the case of a successful response.

Now, back to the request from our client code.  Using a browser means we are in an event loop. Making an HTTP request means we have time when the processor is not being used. Using jQuery means that control is handed back to the browser after a request is sent. The browser regaining control means we have non-blocking IO. Non-blocking IO means that the event loop continues to run whilst it awaits a response. The event loop continuing to run means that other tasks can be dealt with in the mean time.

That deals with the wonders of non-blocking IO and asynchronicity on the client. If we already have all that stuff, I ask, Why is Node.js so special? and I answer, Because this is now easy to do on the server, too.

The server

There is some crazy fucking shit going on in the first line. It uses fu, an imported piece of JavaScript code that acts as a mini router. When you pass a url and a function to fu.get(), you are saying: when the Node.js server gets a request that was sent to this URL, run this function.

A digression on how the router works that explains some things about JavaScript and the Street Hoarding code but that it’s not really necessary to read to get the main points of this article

fu.get() takes a URL and a function and adds the function to a hash, keyed with the URL. fu.listen() starts the server defined by the server variable and makes it listen to events coming to the passed host (probably localhost) on the passed port. We’ve eaten our way around the jam filling, so it’s time to get sticky fingers.

createServer(), a Node.js function, is called with an anonymous function that takes a request and response, and the resulting server object is assigned to the server variable. That anonymous function gets the URL on the passed request object, req, and looks in getMap to find the corresponding function to run. For example, in the latest message code defined above, the url is /latest_message and the function is the rest of the code snippet.

We now meet a second special feature of JavaScript: prototyping. The passed response object, res, has two new methods added to it on the fly: simpleText() and simpleJSON(). The methods themselves are not that interesting – they just create a string to return to the client as a response to its request – it is the fact that they are stuck on the res object without such as a by your leave that I just know is making your head explode.

Finally, the handler function in getMap that corresponded to the requested url is called with the request and the super-charged-with-new-functions response.

Latest message

So, the function passed to fu.get() extracts the since parameter that the client sent with the request. This indicates when the client last received a user message from the server. If the server has received a message from a user since then, sendLatestMessageToClient() is called.

sendLatestMessageToClient() creates a new Redis client. It calls redisClient.stream.addListener() to connect the Redis client to the Redis server, passing a function as the second argument. Note the asynchronicity. The Redis library does not hang around waiting while the Redis client connects to the Redis server. Instead, behind the scenes, it passes control back to the server event loop which, at some point in the future, gets an I’ve Finished My Work And My Name Is The Redis Client Connection Function event which then calls the function passed as the second argument.

This function calls redisClient.lindex() which retrieves the first item in the messages list in the database. Three arguments are passed: the key of the messages list, a 0 to indicate the first item in the list, and yet another callback function. redisClient.lindex() retrieves the first message (did you notice the auxiliary bout of asynchronicity?), and the callback is run which closes the Redis client and runs the simpleJSON() function to send the message back to the client. (Those of us who read the digression are like fully in a special secret club what knows how totally mind-fucking it is that the res object has a simpleJSON() function hanging around on it; those who did not read the digression will keep their heads fuck-free.)

New message

The function passed to fu.get() extracts the message from the request and calls storeMessage(), passing the message and yet another function to call back later.

storeMessage() goes through the familiar routine of creating a Redis client, requesting a connection to the Redis server, calling a Redis function (redisClient.lpush, this time), closing the Redis client and calling back the function passed as the second argument which:

Wait, stop a second. Do you remember how I rather trailed off five paragraphs ago when I wrote, “If the server has received a message from a user since then, sendLatestMessageToClient() is called”? By which I mean, I didn’t say what happened if the server had not received a new message since the last message was sent to the user. Let’s have a look.

Right. Latest message requests that would normally be answered with the message that the client is already displaying are held open. I know that was a long sentence, and this is a long article, and you are tired, but I hope that those last two words didn’t slip by you. Held open. A response is not sent immediately. Instead, a new item is pushed onto the messageRequests array: a hash of the res object and the sendLatestMessageToClient() function.

So, back to the /send_message code to see how it deals with the held message requests. The code extracts the user’s message, stores it and sends a success response back to the client. For each message request that has been pushed onto messageRequests, sendLatestMessageToClient() is called. This sends the latest message (probably the one received a few lines ago) back to the client, thus ending the request. This is Comet: the client sends a request and no response is sent until there is something useful to send, thus the request is held open.

Ryan Dahl did two really cool things. First, he wrote a library that lets you code an event-driven server in JavaScript. However, this was not new. Second, and more importantly, he wrote the core libraries so that they are non-blocking. The problem with other event-driven programming libraries is that you can’t be sure whether the auxiliary libraries you want to use are non-blocking. If they are, you will stall your event loop and it will stop dealing with incoming events and everything will fall apart.

So, from the re-written libraries, we get non-blocking IO, which allows an event loop. The event loop allows the server to run in a single process. A single process means low memory usage.

treating bipolar with abilify and zyprexa

stanozolol dosage connecticut imitrex and zoloft generic plavix and recall grande librairie sp cialis e tunisie substitute for soma

hyzaar allergy

ativan dosage breast augmentation canda acid and calcium carbonate inmediatez tentativa actos preparatorios cephalexin fir strep

shower after breast augmentation

deca winstrol lexapro length of withdrawal bergen county breast augmentation file viewtopic t 144 viagra night sweats and lexapro

premature ejaculation zoloft

ephedrine-extract zoloft synthroid menopause zopiclone prozac can requip cause feet to swell viagra and light sensitivity

dilantin heart medication

diazepam dosage catholic and clomid ontario breast augmentation hgh norway a to z zocor

singulair aerator motor

increase testerone augmentin stealing sun pharma generic plavix effexor withdrawal mayo clinic quit smoking zyban

calcium coral okinawas

how to take ultram and tramadol zoloft micrograms plaintiff's attorney avandia 1871 accutane guild paternity rights guild legal overdose on aleve

brian soma

stanozolol cycle kodak vr35 point and shoot nizoral swish and swallow lamictal and manufacturer tramadol vs vikaden

seagram's green tea ginger ale

buy tramadol tramadol interference with opioid drug testing consumer reports january 2006 zoloft effexor valtrex tramadol erectile dysfunction ed tercel trouble shoot

poppy z brite nudes

ambien for sleep tramadol 50mg dosage evista power candida esophagitis diflucan lisinopril to treat diabetes

natural breast enhancement program actives

ativan lorazepam side effects of dog takin cephalexin bandolino shoot motrin comprimidos cozaar is it safe

hcpcs for cardizem

buy methylphenidate online levaquin long term side effects is nexium better than protonix cute young first phot shoot steve nissen avandia threatened

sar methods chemistry analog tricor

get vicodin smoking on prozac what is in rogaine tylenol and motrin interaction lexapro and sexual side affects

sublingual melatonin retail

buy atarax soma fm streams trouble shoot air conditioner clomid clomiphene near breast cancer risk aleve pets dosage

cialis in the system

cialis is tadalifil diflucan in treatment of valley fever brites disease calan for headaches viagra se games

celebrex drug side effects

buy oxycontin james meister exelon levaquin 500mg and std carl edwards 99 2008 claritin car hoodia pomegran real

hair transplants propecia

dexedrine cost are hives common with synthroid green tea drink neurontin side effects dosage answers to zyban lab

aiya matcha green tea

ephedrine-doseage prednisone dogs shrink tumor clomid iui rate success cla dia almeida sandisk shoot and storage card

breast augmentation centers in mo

get percocet clomid increase cervical fluids mycoplasma pneumonia with cipro lamictal withdrawal symptoms side effects steroid viagra

minnesota green tea weight loss

buy xanax online indications for neurontin archery bow shoots jackson mn atarax 75mg lamisil cream and herpes

aleve gout

clonazepam dosages ruby shoots oswald video hair loss neurontin peach green tea concentrate free sample of cytotec

cla bucholz

fexofenadine and pseudoephedrine tricor fenofibrate tablets injuries caused by accutane inderal red face benefits from effexor

topamax and menopause

get oxycontin hyzaar 100 25 melatonin megadose adalat nasibov how to make green tea extract

actos dosage

benzphetamine hydrochloride green tea adrenal fatigue treating migraines with prozac crestor show results ashwagandha articles

otc melatonin pills

methylphenidate ritalin consiquences for neurontin crestor and adverse reactions iwth cranberries hoodia warnings melatonin getpharma

breast augmentation dd cup

finasteride enlarged prostrate cipro tab atarax 75mg hoodia shortage rhinocort side effects

thai green curry recipe bamboo shoots

clonazepam klonopin graham bunn and model shoot cephalexin and chlamydia lawsuits on celexa mercury intenational assistance and cla

breast augmentation new hampshire

benzphetamine vs phentermine hpt and clomid photos of allegra versace pregnant and taking prilosec price of zyrtec

hoodia buy cheap 34546

atarax side effects shoot through how to shoot a goose zetia blurry vision unknown soldier accutane

ultram tablets

dexedrine dosage green tea home fragrance mist hgh youth fomula aleve and surgery clomid causes

ativan and cipro

cost of fexofenadine articcat trouble shoot allegra beck photo free sample of cytotec us satalite shoot

navt to shoot down satellite

methylphenidate cost cymbalta smoking article altace breast augmentation surgeons in sd dilantin kapseals

contraindications to lipitor

cialis price buspar addictive cialis powered by phpbb kodak vr35 point and shoot ultracet 2000 2002 jelsoft enterprises ltd

cancer femara drug

is oxycodone percocet what is claritin viagras effect on women natural remidies for zoloft withdrawl systems arizona peach green diet tea

crestor mississippi research state university

test propionate prednisone forums hoodia fomula uk buy propecia online cheap pharmacy brite ight bait store

pronounce ultram

diet with phentermine zyprexa litigation has it been settled paxil law suit 2008 new wholesale hoodia diet tabs ultram increased alertness pharmacy

soma neuromuscular integration

test prop vasotec 10mg zyprexa 10 hoodia gordini weightloss 2.5 mg lexapro

tv commercial zyrtec

dosage of sildenafil citrate second month on clomid generic viagra quick shipping fosamax joint pain does maxalt cause heart problems

coral calcium supreme 0d 0a

diazepam and valium dolor ibuprofeno tramadol clomid missed period not pregnant npr prilosec can other drugs interact with cephalexin

matcha japanese green tea

amoxicillin clav diflucan furosemide where to buy nu hgh sprays hgh size needle cymbalta and eyes problems

human growth hormone stimulator

celebrex-coupons zocor competitors buy order discount pravachol free shipping manie heart defects antidepressants paxil what does seroquel treat

symptons of flomax

results of winstrol candida esophagitis diflucan breast augmentation pocket revision lipitor eye can viagra cause surgery complications

prozac band simon

adipex diet absolute best male enhancement pill prednisone for tendinitis melatonin nitemares nexium side affect problems

side effects indomethacin and prednisone combined

amoxicillin and clavulanate tentex royal lipitor trouble urinating colchicine and vision loss does prozac make you fat

prednisone crohn

low testerone naprosyn and tingling cheap fedex tramadol kamagra prescription actonel boniva fosamax side effects

what is in rogaine

fast kamagra viagra and phentermine interaction iv protonix buy tramadol online drugs similiar to viagra

paul vantin

cheap kamagra prednisone cialis combine hoodia 450 migraine coreg grimace shoot

over night cialis

percocet watson 540 evista raloxifene missouri tentex royal johnson zyrtec otc consumer digital cameras shoot image models lenses

diflucan allegra

apap-with-codeine arimidex canada lopressor and jaw pain viagra viagra online buy viagra trouble shoot plumbing

the game shoots fifty

how to buy phentermine cholesterol et nolvadex green vs red tea information on antidepressants like cymbalta photo shoot 4109

lipitor lovastatin

vicodin watson 540 dr mirabile breast augmentation pics atrovent nasal spray 0 03 proscar finasteride hawaii changing from lexapro to effexor

dog poisoning seroquel

fluoxetine dosage pamelor 25mg motrin and headaches casodex bicalutamide texas synthroid and caffeine

augmentation breast financing

celebrex-cost singulair aerator motor allegra print and imaging pekin il evista real dad augmentin 875 mg side effects

people using lexapro

generic vardenafil year of original purim avodart injections is ultram a controlled drug what does glucophage do

medrol dosepak 5mg

get ritalin cost of augmentin 875 rainbow brite dvd viagra and dogs blood cozaar pressure

lexapro peak effects

cheapest acomplia on effexor you cant cum green tea herb and acne can i take tylenol with mobic buy online cozaar

synthroid toxicity

abuse of soma breast augmentation before and after photo effexor overdose effects lexapro drug content and purpose hoodia gordonii complex

rendevous allegra hotel adelaide

percocet vicodin smoking hoodia photo shoot 2096 using cialis with trimix famvir for shingles

beta brite message board

acetaminophen-with-codeine tevau tramadol search tevau tramadol buspar cause early period prednisone and polymyalgia cialis causes muscle ache because

breast augmentation costs in canada

buy acomplia online augmentin 12h neurontin flexeril interaction amaryl and actos korn kobblers don't shoot the bartender

risperdal class action lawsuits

levitra online potential toxic side effect for lasix aciphex 20 mg drug interaction tramadol cymbalta elimite no prescription

azithromycine vs augmentin

finasteride administration dyi photo shoot pronounce ultram affect side zyban accutane guild paternity rights legal dictionary

zoloft and attacks heart

generic soma playboy shoot in bathroom biphasic on 100mg clomid now monophasic celexa effects on voice and speech celebrex inventors

the best melatonin

fluoxetine and alcohol prescription strength aleve augmentin clav k missed dosage prometrium pregnancy picture of cialis

cheap generic zocor

ambien online viagra weight loss can xanax be taken with effexor diabedics taking prednisone prednisone step down dosing dog

green tea help w allergies

ativan xanax mega green tea diet health mens dose green tea help lose weight flomax australia blood pressure rise medication lisinopril

green tea prices

cheap viagra does maxalt cause heart problems clomid testicular axis lexapro spinal therapy bodybuilding ultimate hgh bodybuilding

different prozac formulas

adipex and alcohol prozac klonopin albers medical lipitor diet hoodia supplement decafinated mega green tea extract