Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of git://github.com/creationix/howtonode.org

  • Loading branch information...
commit 091708cdb17b7fe89c868ee6a219ab4bc5cfccdd 2 parents 2f7f451 + e4a9219
@codelotus codelotus authored
Showing with 917 additions and 67 deletions.
  1. +1 −0  app.js
  2. +124 −0 articles/capturing-packets-in-javascript.markdown
  3. +15 −0 articles/capturing-packets-in-javascript/example.js
  4. BIN  articles/capturing-packets-in-javascript/http_trace_1.jpg
  5. BIN  articles/capturing-packets-in-javascript/http_trace_2.jpg
  6. BIN  articles/capturing-packets-in-javascript/http_trace_3.jpg
  7. BIN  articles/capturing-packets-in-javascript/pcap_boxes.png
  8. +122 −0 articles/intro-to-jake.markdown
  9. +7 −0 articles/intro-to-jake/async-jakefile.js
  10. +20 −0 articles/intro-to-jake/namespace-jakefile.js
  11. +5 −0 articles/intro-to-jake/sample-jakefile.js
  12. +102 −0 articles/object-graphs.markdown
  13. +41 −0 articles/object-graphs/closure.dot
  14. +9 −0 articles/object-graphs/closure.js
  15. +35 −0 articles/object-graphs/functions.dot
  16. +18 −0 articles/object-graphs/functions.js
  17. +26 −0 articles/object-graphs/globals.dot
  18. +5 −0 articles/object-graphs/globals.js
  19. +37 −0 articles/object-graphs/objects.dot
  20. +14 −0 articles/object-graphs/objects.js
  21. +21 −0 articles/object-graphs/variables.dot
  22. +7 −0 articles/object-graphs/variables.js
  23. +34 −0 articles/testing-your-javascript.markdown
  24. +25 −0 articles/testing-your-javascript/mario_tests.js
  25. +118 −0 articles/websockets-socketio.markdown
  26. +16 −0 articles/websockets-socketio/broadcasts.js
  27. +6 −0 articles/websockets-socketio/longpolling.js
  28. +8 −0 articles/websockets-socketio/socketio-sample.js
  29. +5 −0 authors/Guillermo Rauch.markdown
  30. +5 −0 authors/Matt Ranney.markdown
  31. +5 −0 authors/Phil Crosby.markdown
  32. +34 −34 skin/article.haml
  33. +15 −16 skin/index.haml
  34. +8 −7 skin/layout.haml
  35. +3 −2 skin/public/print.css
  36. +26 −8 skin/public/volcano.css
View
1  app.js
@@ -4,6 +4,7 @@ var Connect = require('connect');
module.exports = Connect.createServer(
Connect.logger(),
Connect.conditionalGet(),
+ Connect.favicon(),
Connect.cache(),
Connect.gzip(),
require('wheat')(__dirname)
View
124 articles/capturing-packets-in-javascript.markdown
@@ -0,0 +1,124 @@
+Title: Capturing Packets in JavaScript with node_pcap
+Author: Matt Ranney
+Date: Wed Sep 29 2010 23:08:34 GMT-0700 (PDT)
+
+OK, I hear you. Capturing packets is hard and best left to kernel hackers, assembly language programmers, and black hat security
+researches. If you just want to make things for the web using node.js, why should you care?
+
+Pulling packets off the network can show you what your computers are saying to each other without disrupting the flow of or changing any applications. Packet capture is a fantastic debugging tool that will remove a lot of the mystery from writing and running network programs. The point of `node_pcap` is to provide a good HTTP debugging tool and a framework for doing your own network analysis.
+
+There are plenty of ways to do packet inspection these days, but none of them let you interact with your network traffic the way that node lets you write network programs: by writing a few event handlers in JavaScript. `node_pcap` not only let's you capture and process packets in JavaScript, but since it is built on node.js, data from the packets can be easily routed around to web browsers, databases, or whatever else you can think of.
+
+## Example
+
+Here's an example of capturing packets and sending them back to a web browser using WebSocket:
+
+<http://pcap.ranney.com:81/>
+
+If you still aren't convinced, check out how easy it is to write a simple "network grep" type of program using `node_pcap`:
+
+<capturing-packets-in-javascript/example.js>
+
+This program will look at all TCP packets that flow past the default network interface and run the regular expression `matcher` against the data section of the packet. If it matches, the data section will be printed.
+
+Still not convinced? I understand. This packet business can be astonishingly low level compared to the abstractions you are comfortable working with. If this doesn't seem awesome yet, it probably won't until you actually need it. When you can't figure out what your program is doing by just adding log messages, come back and check out what packet capture can do for you.
+
+`node_pcap` exposes packets as JavaScript objects, but it also comes with a few examples that are useful on their own. If you do nothing else, check out `http_trace` and `simple_capture`. Look at the source code and see how they work. It's really easy.
+
+## Installation
+
+Anyway, if you are still here, let's get this sucker installed. The first thing you'll need is `libpcap`. If you are on OSX 10.6, you already have it. If you are on a Linux system that uses `apt-get` to install things, you can get it like this:
+
+ sudo apt-get install libpcap-dev
+
+
+If you are on some other kind of system, I don't know the exact command to install `libpcap-dev`, but it is a very common library that's widely available.
+
+Once you have `libpcap` and node, you just need `npm`. Install `node_pcap` with `npm` like this:
+
+ npm install pcap
+
+This will install the pcap libraries and three executable.
+
+If you want to hack on the code, and I encourage you to do so, use `git` to clone the repository on github:
+
+ git clone git://github.com/mranney/node_pcap.git
+
+You'll still need to use `npm` to build and install the files where they need to go:
+
+ mjr:~/work/node_pcap$ npm install .
+
+To verify that things are working, run:
+
+ sudo simple_capture
+
+It should look something like this:
+
+ mjr:~$ sudo simple_capture
+ libpcap version 1.0.0
+ * en0 10.51.2.125/255.255.255.0
+ fw0 no address
+ en1 no address
+ lo0 127.0.0.1/255.0.0.0
+ 00:1c:23:b9:e8:b5 -> ff:ff:ff:ff:ff:ff 10.51.2.10 ARP request 10.51.2.4
+ 00:1e:c9:45:e8:30 -> ff:ff:ff:ff:ff:ff 10.51.2.1 ARP request 10.51.2.45
+ 00:1a:92:c4:32:d1 -> ff:ff:ff:ff:ff:ff 10.51.2.179 ARP request 10.51.2.126
+
+Your traffic might not be ARP requests, but some packets should be flowing, and you should see one line per packet.
+
+Opening the capture interface on most operating systems requires root access, so most of the time that you run a program using `node_pcap` you'll need to use sudo.
+
+## http_trace
+
+`http_trace` is a tool that distills the packets involved in an HTTP session into higher level events. There are command line options to adjust the output and select different requests. Here's a simple example of looking for any requests that have "favicon" in the URL and showing request and response headers:
+
+<img src="capturing-packets-in-javascript/http_trace_1.jpg" style="float: none"></img>
+
+
+To see the full list of options do:
+
+ http_trace --help
+
+With no arguments, `http_trace` will listen on the default interface for any IPv4 TCP traffic on any port. If it finds HTTP on any TCP connection, it'll start decoding it. You might be surprised by how many HTTP connections your computer is making that you didn't know about, especially if you run OSX. Fire it up and see what you find.
+
+## Solving Problems
+
+Here's why you need all of this. Let's say you have a node program that makes an outgoing connection, but the outgoing connection doesn't seem like it is working. This reason in this case is that a firewall rule is filtering the traffic. Here's how to detect it:
+
+<img src="capturing-packets-in-javascript/http_trace_2.jpg" style="float: none"></img>
+
+The `--tcp-verbose` option will expose events for TCP connection setup, close, and reset. It'll also let you know about SYN retries and packets retransmissions. SYN retry happens when a new TCP connection is getting set up, but the other side isn't responding. Retransmissions occur when packets are dropped by the network, and TCP on either end of the connection resends data that has already sent. If data is moving slowly, but you don't appear to be out of CPU, turn on `--tcp-verbose` and see if you are getting retransmissions or SYN retries. If so, you can blame the network and not your node program.
+
+Another common case is when the data going over the network isn't quite the data you were expecting. Here's a simple example using curl from the command line. Let's say you wanted to send some JSON to your local CouchDB, but CouchDB keeps rejecting it.
+
+ mjr:~$ curl -X POST 'http://localhost:5984/test' -H "Content-Type: application/json" -d {"foo": "bar"}
+ {"error":"bad_request","reason":"invalid UTF-8 JSON"}
+
+That looks like pretty well-formed JSON, so what's going on here? Run `http_trace` with the --bodies option to dump the request and response body. Since this is a connection to `localhost`, we need to explicitly listen on the loopback interface.
+
+<img src="capturing-packets-in-javascript/http_trace_3.jpg" style="float: none"></img>
+
+Here we can see that the request body was simply, "{foo:", which is clearly not valid JSON. The problem in this case is that the shell and curl couldn't figure out what part of the command line arguments to use for the POST body, and they got it wrong. This works if quoted properly:
+
+ mjr:~$ curl -X POST 'http://localhost:5984/test' -H "Content-Type: application/json" -d '{"foo": "bar"}'
+ {"ok":true,"id":"b4385e0de2e74df4cdbf21cf6c0009d0","rev":"1-4c6114c65e295552ab1019e2b046b10e"}
+
+## Understanding Higher Level Protocols
+
+`node_pcap` can piece back together a TCP session from individual packets as long as it sees them all go by. It will emit events at TCP connection setup, teardown, and reset.
+
+On top of TCP, it can decode HTTP and WebSocket messages, emitting events for request, response, upgrade, data, etc.
+
+It looks sort of like this:
+
+<img src="capturing-packets-in-javascript/pcap_boxes.png" style="float: none"></img>
+
+You set up `node_pcap` to capture the packets you want, and then you can work with the captured data in JavaScript at whatever level is the most useful.
+
+## Work in Progress
+
+There are a lot of cases that `node_pcap` doesn't handle, and for these you'll need a more complete packet decoder like Wireshark. I'm trying to handle the common case of OSX/Linux, IPv4, TCP, HTTP, and WebSocket first, and then add support for other variants of the protocol stack.
+
+If you like this kind of stuff and want to help expand the protocols that node_pcap understands, patches are certainly welcome.
+
+I hope this software is useful and fun. Thanks for reading.
View
15 articles/capturing-packets-in-javascript/example.js
@@ -0,0 +1,15 @@
+var pcap = require("pcap"),
+ pcap_session = pcap.createSession("", "tcp"),
+ matcher = /safari/i;
+
+console.log("Listening on " + pcap_session.device_name);
+
+pcap_session.on('packet', function (raw_packet) {
+ var packet = pcap.decode.packet(raw_packet),
+ data = packet.link.ip.tcp.data;
+
+ if (data && matcher.test(data.toString())) {
+ console.log(pcap.print.packet(packet));
+ console.log(data.toString());
+ }
+});
View
BIN  articles/capturing-packets-in-javascript/http_trace_1.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  articles/capturing-packets-in-javascript/http_trace_2.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  articles/capturing-packets-in-javascript/http_trace_3.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  articles/capturing-packets-in-javascript/pcap_boxes.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
122 articles/intro-to-jake.markdown
@@ -0,0 +1,122 @@
+Title: Intro to Jake - JavaScript build tool for Node.js
+Author: Matthew Eernisse
+Date: Wed Sep 29 2010 12:39:01 GMT+0530 (IST)
+
+Jake is a JavaScript build program for Node.js, with capabilities similar to GNU Make or Ruby's Rake. If you've ever built projects with Rake, you'll be very at home using Jake
+
+Jake has the following features:
+
+* Jakefiles are in standard JavaScript syntax
+* Tasks with prerequisites
+* Namespaces for tasks
+* Async execution of tasks
+
+## Installing
+
+**Requirements**: Jake requires Node.js. Of course.
+
+Get Jake:
+
+ git clone git://github.com/mde/node-jake.git
+
+Build Jake:
+
+ cd node-jake && make && sudo make install
+
+Or install with NPM:
+
+ npm install jake
+
+(Or, get the code, and `npm link` in the code root.)
+
+## Basic usage
+
+ jake [options] target (commands/options ...)
+
+You can see what other options Jake supports by doing `jake -h` or `jake --help` Probably the most important option starting off is the `-T` or `--tasks` option that lets you see what tasks are defined in a Jakefile.
+
+## Jakefiles and Jakefile syntax
+
+You define your build tasks in a Jakefile.js (usually in the root of your project) -- by default the `jake` command looks for a Jakefile in the current directory, but you can also point Jake at a specific file using the -f (--jakefile) flag.
+
+In your Jakefile, call `task` to define tasks. Call it with three arguments (and one more optional argument):
+
+ task(name, dependencies, handler, [async]);
+
+Where `name` is the string name of the task, `dependencies` is an array of the dependencies, and `handler` is a function to run for the task.
+
+Here's an example:
+
+<intro-to-jake/sample-jakefile.js>
+
+This is a build tool for Node, so of course we want to support async execution, right? The `async` argument is optional, and when set to true `(async === true)` indicates the task executes asynchronously. Asynchronous tasks need to call `complete()` to signal they have completed.
+
+Here's an example of an asynchronous task:
+
+<intro-to-jake/async-jakefile.js>
+
+Use `desc` to add a string description of the task.
+
+Use `namespace` to create a namespace of tasks to perform. Call it with two arguments:
+
+ namespace(name, namespaceTasks);
+
+Where is `name` is the name of the namespace, and `namespaceTasks` is a function with calls inside it to `task` or `desc` defining all the tasks for that namespace.
+
+Here's an example:
+
+<intro-to-jake/namespace-jakefile.js>
+
+In this example, the foo:baz task depends on both the 'default' and the 'foo:bar' task.
+
+Run these namespaced tasks with `jake [namespace]:[task]`. The above example would be run with:
+
+ jake foo:baz
+
+## Passing parameters to jake
+
+You can pass either positional or named parameters to Jake tasks (well, 'named parameters,' the JavaScripty way, in an Object).
+
+Single parameters passed to the jake command after the task name are passed along to the handler as positional arguments.
+
+So you can see this in action, let's set up an 'awesome' task that does nothing but print out the arguments it gets:
+
+ desc('This is an awesome task.');
+ task('awesome', [], function () {
+ console.log(sys.inspect(Array.prototype.slice.call(arguments)));
+ });
+
+With this example, running `jake` like this:
+
+ jake awesome foo bar baz
+
+You'd get the following output:
+
+ [ 'foo', 'bar', 'baz' ]
+
+Paramters passed to the jake command that contain a colon (:) or equals sign (=) will be added to a keyword/value object that is passed as a final argument to the handler.
+
+With the above Jakefile, you could run `jake` like this:
+
+ jake awesome foo bar baz qux:zoobie frang:asdf
+
+And you'd get the following output:
+
+ [ 'foo'
+ , 'bar'
+ , 'baz'
+ , { qux: 'zoobie', frang: 'asdf' }
+ ]
+
+As you might expect if you're used to Make or Rake, running `jake` with no arguments runs the default task.
+
+## Related projects
+
+James Coglan's "Jake": <http://github.com/jcoglan/jake>
+
+Confusingly, this is a Ruby tool for building JavaScript packages from source code.
+
+280 North's Jake: <http://github.com/280north/jake>
+
+This is also a JavaScript port of Rake, but it runs on the JVM-based Narwhal platform.
+
View
7 articles/intro-to-jake/async-jakefile.js
@@ -0,0 +1,7 @@
+desc('This is an asynchronous task.');
+task('asynchronous', [], function () {
+ setTimeout(function () {
+ console.log("Yay, I'm asynchronous!");
+ complete();
+ }, 1000);
+}, true);
View
20 articles/intro-to-jake/namespace-jakefile.js
@@ -0,0 +1,20 @@
+desc('This is the default task.');
+task('default', [], function () {
+ console.log('This is the default task.');
+ console.log(sys.inspect(arguments));
+});
+
+namespace('foo', function () {
+ desc('This the foo:bar task');
+ task('bar', [], function () {
+ console.log('doing foo:bar task');
+ console.log(sys.inspect(arguments));
+ });
+
+ desc('This the foo:baz task');
+ task('baz', ['default', 'foo:bar'], function () {
+ console.log('doing foo:baz task');
+ console.log(sys.inspect(arguments));
+ });
+
+});
View
5 articles/intro-to-jake/sample-jakefile.js
@@ -0,0 +1,5 @@
+desc('This is the default task.');
+task('default', [], function (params) {
+ console.log('This is the default task.');
+ console.log(sys.inspect(arguments));
+});
View
102 articles/object-graphs.markdown
@@ -0,0 +1,102 @@
+Title: Learning Javascript with Object Graphs
+Author: Tim Caswell
+Date: Thu Sep 30 2010 13:48:55 GMT-0700 (PDT)
+Node: v0.2.2
+
+One of the secrets to being a super effective JavaScript developer is to truly understand the semantics of the language. This article will explain the basic elemental parts of JavaScript using easy to follow diagrams.
+
+## References Everywhere
+
+A variable in JavaScript is simply a label that references a value in memory somewhere. These values can be primitives like strings, numbers, and booleans. They can also be objects or functions.
+
+### Local Variables
+
+In the following example, we will create four local variables in the top-level scope and point them to some primitive values:
+
+<object-graphs/variables.js*>
+
+![variables](object-graphs/variables.dot)
+
+Notice that the two boolean variables point to the same value in memory. This is because primitives are immutable and so the VM can optimize and share a single instance for all references to that particular value.
+
+In the code snippet we checked to see if the two references pointed to the same value using `===` and the result was `true`.
+
+The outer box represents the outermost closure scope. These variables are top-level local variables, not to be confused with properties of the global/window object.
+
+<br style="clear:left"/>
+
+### Objects and Prototype Chains
+
+Objects are just collections of more references to new objects and prototypes. The only special thing they add is the prototype chain for when you try to access a property that's not in the local object, but is in a parent object.
+
+<object-graphs/objects.js*>
+
+![shared-function](object-graphs/objects.dot)
+
+<br style="clear:left"/>
+
+Here we have one object with four properties referenced by the `tim` variable. Also we created a new object that inherits from the first object and referenced it from `jack`. Then we overrode two properties in the local object.
+
+Now when looking up `jack.likesJavaScript`, we first find the object that `jack` references. Then we look for the `likesJavaScript` property. Since it's not there, we look at the parent object and find it there. Then we find the `true` value it references.
+
+### The Global Object
+
+Ever wondered why tools like [jslint][] always tell you to not forget to put `var` statements before your variables. Well, here is what happens if you forget.
+
+<object-graphs/globals.js>
+
+![variables](object-graphs/globals.dot)
+
+Notice that `likesJavaScript` is now a property of the global object instead of a free variable in the outer closure. This only really matters if you're going to be mixing several scripts. But in any real program that's exactly what you're going to be doing.
+
+Always remember to put those `var` statements in there to keep your variable's scope to the current closure and it's children. You'll be much happier by following this simple rule.
+
+If you must put something on the global object, do it explicitly with `window.woo` in the browser or `global.goo` in node.js.
+
+<br style="clear:left"/>
+
+## Functions and Closures
+
+JavaScript isn't just a series of chained data structures. It contains executable, callable code known as functions. These functions create chained scopes and closures.
+
+### Visualizing Closures
+
+Functions can be drawn as special objects that contain executable code as well as properties. Every function has a special `[scope]` property that represents the environment it was in when it was defined. If a function is returned from another function then this reference to the old environment is closed over by the new function in a "closure".
+
+In this example we will create a simple factory method that generates a closure and returns a function.
+
+<object-graphs/closure.js*>
+
+![variables](object-graphs/closure.dot)
+
+<br style="clear:left"/>
+
+When we call `description1()`, the VM looks up the function that it references and executes it. Since that function looks for a local variable named `name`, it finds it in the closure scope. This factory method is nice since each generated function has it's own space for local variables.
+
+See the article [why use closure][] for more in-depth reading on this topic and it's many uses.
+
+### Shared Functions and `this`
+
+Sometimes for performance reasons, or because you just plain prefer the style, JavaScript provides a `this` keyword that allows you to reuse a function object in different scopes depending on how it was called.
+
+Here we'll create a few objects that all share a common function. This function will reference `this` internally to show how it changes from call to call.
+
+<object-graphs/functions.js*>
+
+![variables](object-graphs/functions.dot)
+
+<br style="clear:left"/>
+
+In the diagram, we see that even though `Fred.description` was set to `Lane.description`, it's really only referencing the function. Thus all three references have equal ownership of the anonymous function. This is why I try to not call functions on constructor prototypes "methods", because that implies some sort of binding of the function to the constructor and it's "class". *(see [what is this][] for more details on the dynamic nature of `this`)*
+
+## Conclusion
+
+I've had tons of fun using diagrams to visualize these data structures. My hope is that this helps those of us that are visual learners to get a better grasp of JavaScript semantics. I have past experience as both a front-end designer/developer and as a server-side architect. I hope my unique perspective is useful to those coming from the world of design and learning the innards of this wonderful language known as JavaScript.
+
+*(NOTE, all the diagrams are [graphviz][] dot files and can be seen [here][])*
+
+[jslint]: http://jslint.com/
+[what is this]: http://howtonode.org/what-is-this
+[why use closure]: http://howtonode.org/why-use-closure
+[graphviz]: http://www.graphviz.org/
+[here]: http://github.com/creationix/howtonode.org/tree/master/articles/object-graphs/
View
41 articles/object-graphs/closure.dot
@@ -0,0 +1,41 @@
+digraph finite_state_machine {
+ size="8,5"
+ rankdir = "LR"
+
+ /* Execution Contexts */
+ node [shape=Mrecord, fillcolor=beige, style=filled];
+ top [label="<__proto__>[ Scope ]|<description1>description1|<description2>description2|<makeClosure>makeClosure"];
+ closure1 [label="<__proto__>[ Scope ]|<name>name"];
+ closure2 [label="<__proto__>[ Scope ]|<name>name"];
+
+ /* Normal Objects */
+ node [shape = Mrecord, fillcolor=lightskyblue, style=filled];
+
+ /* Function Objects */
+ node [shape = Mrecord, fillcolor=orange, style=filled];
+ fn_makeClosure [label="<__proto__>[ makeClosure(name) ]|\{ return function () \{\} \}"];
+ fn1 [label="<__proto__>[ λ() ]|\{return name\}|<__scope__>[scope]"];
+ fn2 [label="<__proto__>[ λ() ]|\{return name\}|<__scope__>[scope]"];
+
+ /* Literals */
+ node [shape = plaintext, fillcolor=gray92, style="filled,rounded"];
+ cloe [label="\"Cloe the Closure\""]
+ albert [label="\"Albert the Awesome\""]
+
+ /* References */
+ top:makeClosure -> fn_makeClosure:__proto__;
+ top:description1 -> fn1:__proto__;
+ top:description2 -> fn2:__proto__;
+ closure1:name -> cloe;
+ closure2:name -> albert;
+ fn1:__scope__ -> closure1:__proto__;
+ fn2:__scope__ -> closure2:__proto__;
+
+ /* Inheritance Chains */
+ edge [style=dashed]
+ closure1:__proto__ -> top:__proto__;
+ closure2:__proto__ -> top:__proto__;
+
+
+
+}
View
9 articles/object-graphs/closure.js
@@ -0,0 +1,9 @@
+function makeClosure(name) {
+ return function () {
+ return name;
+ };
+}
+var description1 = makeClosure("Cloe the Closure");
+var description2 = makeClosure("Albert the Awesome");
+console.log(description1());
+console.log(description2());
View
35 articles/object-graphs/functions.dot
@@ -0,0 +1,35 @@
+digraph finite_state_machine {
+ size="8,5"
+ rankdir = "LR"
+
+ /* Execution Contexts */
+ node [shape=Mrecord, fillcolor=beige, style=filled];
+ top [label="<__proto__>[ Scope ]|<Lane>Lane|<description>description|<Fred>Fred"];
+
+ /* Normal Objects */
+ node [shape = Mrecord, fillcolor=lightskyblue, style=filled];
+ ob_Lane [label = "<__proto__>[ Object ]|<name>name|<description>description"];
+ ob_Fred [label = "<__proto__>[ Object ]|<description>description|<name>name"];
+ ob_1 [label = "<__proto__>[ Object ]|<name>name"];
+
+ /* Function Objects */
+ node [shape = Mrecord, fillcolor=orange, style=filled];
+ fn1 [label="<__proto__>[ λ() ]|\{return this.name\}"];
+
+ /* Literals */
+ node [shape = plaintext, fillcolor=gray92, style="filled,rounded"];
+ name [label="\"Lane the Lambda\""]
+ name2 [label="\"Fred the Functor\""]
+ name3 [label="\"Zed the Zetabyte\""]
+
+ /* References */
+ top:Lane -> ob_Lane:__proto__;
+ top:Fred -> ob_Fred:__proto__;
+ ob_Lane:name -> name;
+ ob_Lane:description -> fn1:__proto__;
+ ob_Fred:name -> name2;
+ ob_Fred:description -> fn1:__proto__;
+ ob_1:name -> name3;
+ top:description -> fn1:__proto__;
+
+}
View
18 articles/object-graphs/functions.js
@@ -0,0 +1,18 @@
+var Lane = {
+ name: "Lane the Lambda",
+ description: function () {
+ return this.name;
+ }
+};
+var description = Lane.description;
+var Fred = {
+ description: Lane.description,
+ name: "Fred the Functor"
+};
+// Call the function from four different scopes
+console.log(Lane.description());
+console.log(Fred.description());
+console.log(description());
+console.log(description.call({
+ name: "Zed the Zetabyte"
+}));
View
26 articles/object-graphs/globals.dot
@@ -0,0 +1,26 @@
+digraph finite_state_machine {
+ size="8,5"
+ rankdir = "LR"
+
+ /* Execution Contexts */
+ node [shape=Mrecord, fillcolor=beige, style=filled];
+ top [label="<__proto__>[ Scope ]|<name>name|<age>age|<isProgrammer>isProgrammer"];
+ global [label="<__proto__>[ global ]|<likesJavaScript>likesJavaScript"];
+
+ /* Literals */
+ node [shape = plaintext, fillcolor=gray92, style="filled,rounded"];
+ timcaswell [label="\"Tim Caswell\""]
+ n28 [label="28"]
+ true [label="true"]
+
+ /* References */
+ top:name -> timcaswell;
+ top:age -> n28;
+ top:isProgrammer -> true;
+ global:likesJavaScript -> true;
+
+ /* Inheritance Chains */
+ edge [style=dashed]
+ top:__proto__ -> global:__proto__;
+
+}
View
5 articles/object-graphs/globals.js
@@ -0,0 +1,5 @@
+var name = "Tim Caswell";
+var age = 28;
+var isProgrammer = true;
+// Oops we forgot a var
+likesJavaScript = true;
View
37 articles/object-graphs/objects.dot
@@ -0,0 +1,37 @@
+digraph finite_state_machine {
+ size="8,5"
+ rankdir = "LR"
+
+ /* Execution Contexts */
+ node [shape=Mrecord, fillcolor=beige, style=filled];
+ top [label="<__proto__>[ Scope ]|<tim>tim|<jack>jack"];
+
+ /* Normal Objects */
+ node [shape = Mrecord, fillcolor=lightskyblue, style=filled];
+ obj1 [label = "<__proto__>[ Object ]|<name>name|<age>age|<isProgrammer>isProgrammer|<likesJavaScript>likesJavaScript"];
+ obj2 [label = "<__proto__>[ Object ]|<name>name|<age>age"];
+
+
+ /* Literals */
+ node [shape = plaintext, fillcolor=gray92, style="filled,rounded"];
+ s_tim [label="\"Tim Caswell\""]
+ s_jack [label="\"Jack Caswell\""]
+ true [label="true"]
+ n4 [label="4"]
+ n28 [label="28"]
+
+ /* References */
+ top:tim -> obj1:__proto__;
+ obj1:name -> s_tim;
+ obj1:age -> n28;
+ obj1:isProgrammer -> true;
+ obj1:likesJavaScript -> true;
+ top:jack -> obj2:__proto__;
+ obj2:name -> s_jack;
+ obj2:age -> n4;
+
+ /* Inheritance Chains */
+ edge [style=dashed]
+ obj2:__proto__ -> obj1:__proto__;
+
+}
View
14 articles/object-graphs/objects.js
@@ -0,0 +1,14 @@
+// Create a parent object
+var tim = {
+ name: "Tim Caswell",
+ age: 28,
+ isProgrammer: true,
+ likesJavaScript: true
+}
+// Create a child object
+var jack = Object.create(tim);
+// Override some properties locally
+jack.name = "Jack Caswell";
+jack.age = 4;
+// Look up stuff through the prototype chain
+jack.likesJavaScript;
View
21 articles/object-graphs/variables.dot
@@ -0,0 +1,21 @@
+digraph finite_state_machine {
+ size="8,5"
+ rankdir = "LR"
+
+ /* Execution Contexts */
+ node [shape=Mrecord, fillcolor=beige, style=filled];
+ top [label="<__proto__>[ Scope ]|<name>name|<age>age|<isProgrammer>isProgrammer|<likesJavaScript>likesJavaScript"]
+
+ /* Literals */
+ node [shape = plaintext, fillcolor=gray92, style="filled,rounded"];
+ timcaswell [label="\"Tim Caswell\""]
+ n28 [label="28"]
+ true [label="true"]
+
+ /* References */
+ top:name -> timcaswell;
+ top:age -> n28;
+ top:isProgrammer -> true;
+ top:likesJavaScript -> true;
+
+}
View
7 articles/object-graphs/variables.js
@@ -0,0 +1,7 @@
+// Let's create some local variables in the top scope
+var name = "Tim Caswell";
+var age = 28;
+var isProgrammer = true;
+var likesJavaScript = true;
+// Test to see if the two variables reference the same value
+isProgrammer === likesJavaScript;
View
34 articles/testing-your-javascript.markdown
@@ -0,0 +1,34 @@
+Title: Testing your JavaScript with shoulda.js
+Author: Phil Crosby
+Date: Wed Sep 29 2010 23:59:28 GMT-0700 (PDT)
+
+The last thing you want this weekend is to be introducing bugs at 4:30PM on Sunday as you frantically hack in more functionality. One way to avoid that risk is to write some tests for your critical, stable bits. If you have time this weekend to hack on unit tests in between the red-bull-induced coma and the confused debates about sockets vs. ports (unlikely!), [shoulda.js](http://github.com/philc/shoulda.js) will help you get up and running very quickly.
+
+Shoulda.js is a micro javascript unit testing framework inspired by Thoughtbot's Shoulda for Ruby. It gives you a tight syntax for writing terse, readable unit tests. It weighs in at under 300 lines and makes no assumptions about your javascript environment or libraries.
+
+## Get Started
+
+Here's how to get started. You've probably seen this pattern before: tests are grouped into logical units called "contexts". Contexts can optionally share test-case setup code. This is a sample test for a massively concurrent version of super mario we've written (the source for that is not included ;-) :
+
+<testing-your-javascript/mario_tests.js>
+
+You can run this file using v8:
+
+ $ v8 mario_test.js
+ Pass (19/19)
+
+## Stubbing
+
+The key to writing narrow, readable unit tests is stubbing out functionality to make your test easier to read and write. You'll often want to stub out expensive methods like talking to the network, or methods you want to manipulate in a special way to set up your test. Shoulda.js provides this syntax for stubbing out properties and methods:
+
+ stub(document, "getElementById", function(id) { assert.equal(id, "marioCharacter"); });
+
+or
+
+ stub(document, "getElementById", returns(myElement));
+
+## Writing client side unit tests for the browser.
+
+If you want to write tests which need to leverage a full browser environment, use envjs. You can then test your client side javascript code from the command line, without having to launch an external browser.
+
+Enjoy! And good luck.
View
25 articles/testing-your-javascript/mario_tests.js
@@ -0,0 +1,25 @@
+load("mario.js");
+
+context("super mario",
+ setup(function() {
+ game = new SuperMarioGame();
+ }),
+
+ context("enemy interaction",
+ setup(function() {
+ turtle = game.addTurtleEnemy({ x: 10, y: 0 });
+ }),
+
+ should("kill the turtle after jumping on it", function() {
+ game.mario.jump({ x: 10, y: 0 });
+ assert.equal("dead", turtle.state);
+ }),
+
+ should("end the game if mario walks into an enemy turtle", function() {
+ game.mario.move({ x: 10, y: 0 });
+ assert.equal("gameOver", game.state);
+ })
+ )
+);
+
+Tests.run();
View
118 articles/websockets-socketio.markdown
@@ -0,0 +1,118 @@
+Title: Websockets everywhere with Socket.IO
+Author: Guillermo Rauch
+Date: Wed Sep 29 2010 12:11:39 GMT+0530 (IST)
+
+If you've stayed on top of the advances in the realtime web for the past few years, you've probably heard of different techniques aimed to reduce the latency (ie: speed) of the message exchange between a client and a server. If you're developing a multiplayer game, a chat application, or showing frequent updates of data like tweets or stock price changes, you probably want to **reverse** the traditional model of communication. So instead of requesting (polling) data on a specific interval of time, you want the server to send (push) data to the client.
+
+Nowadays, terms like `long polling`, `comet` and `WebSocket` come to mind when it comes to developing a realtime web application or widget. But it's not always obvious how they work, their advantages, disadvantages and limitations, or even what percentage of the web browser market share supports them.
+
+[Socket.IO](http://socket.io) is a lightweight API that runs on the browser and looks like this:
+
+<websockets-socketio/socketio-sample.js>
+
+If you're familiar with `WebSocket`, the protocol that aims to simplify bi-directional communication over HTTP once and for all, you'll notice that it looks **very** similar. The difference is that Socket.IO, under the hood, will enable realtime communication for IE6-9, Firefox 3-4, Safari 3-5, Chrome 3-6, iOS (iPhone and iPad), and other commonplace user agents.
+
+## Revisiting the history of the realtime web
+
+In this day and age, odds are that if you're a web developer you've used AJAX once or twice. Very much like what socket.io does for realtime, libraries like `jQuery` have provided abstractions that aim to remove the incompatibilities of what browsers offer for asynchronous HTTP requests (IE uses a proprietary ActiveX object, and mostly everyone else uses the standard XMLHttpRequest).
+
+Now, if you wanted to make a realtime widget that retrieves data from the server, your first idea might look somewhat like this:
+
+ setInterval(function(){
+ $.ajax({ url: '/my/page', success: function(data){
+ // do something with the data
+ } });
+ }, 5000);
+
+So, every 5 seconds we `poll` the server for new updates. In my book, that's almost as efficient as the [transmission of IP Datagrams on pigeons](http://www.rfc-editor.org/rfc/rfc1149.txt).
+
+You might also want to try to reduce the interval, and say, put it at 100 milliseconds.
+
+ setInterval(function(){
+ // now this should be fast!
+ $.ajax({ url: '/my/page', success: function(data){} });
+ }, 100);
+
+However, we're ignoring two major downsides now:
+
+* The HTTP latency. Chances are that a complete roundtrip of the packets on a high speed internet connection will be around 200ms. But this is not always the case! If it were 500 or higher, then things might slow down. And they might slow down unnecessarily, because:
+* The server might not have any new data for us. In this case, we'd be producing a lot of network traffic, and request/response cycles overhead, for no purpose.
+
+## Introducing long polling
+
+Long polling addresses the weakness of traditional polling methods by asking the server for new information on a certain interval, but keeping the connection open if the server has nothing new for us. This technique dramatically decreases latency and network traffic, which means it efficiently disguises itself as a server-push technique.
+
+ function load(){
+ $.ajax({ url: '/my/page', success: function(){
+ // do something with the data
+ }, complete: load, timeout: 20000 });
+ }
+
+## How about keeping the connection open?
+
+If you come from a more traditional programming environment (eg: Desktop software), you're probably wondering why we don't keep the connection open.
+
+This is possible with at least two fairly well known techniques:
+
+* XMLHttpRequest and the `multipart/x-mixed-replace` MIME type (which is enabled by setting `multipart = true` in the XMLHTTPRequest instance)
+
+Although it was introduced by Netscape in 1995 (yes, when some of us were still unable to read properly), the only commonplace user agent to support it is Firefox.
+
+* An `<iframe>` populated with a response with the headers `Transfer-encoding: chunked` and `Connection: keep-alive`.
+
+The technique consists of writing `<script>` tags that call a function on the parent page as information becomes available to push to the client.
+
+The disadvantage of this method is that it'll trigger a never-ending spinner or progress bar in most user agents, severely hurting the user experience. In Internet Explorer, this can be worked around by inserting the `<iframe>` in a hidden document (via the obscure ActiveX object `htmlfile`). This technique was exposed to me for the first time thanks to the Gmail Chat team. This gem was [analyzed/discovered](http://infrequently.org/2006/02/what-else-is-burried-down-in-the-depths-of-googles-amazing-javascript/) back in the day by Alex Russell
+
+By now, it's obvious that some lower-latency techniques are available to certain user agents, under certain conditions. The fundamental problem is that now the server has to treat HTTP requests differently, altering
+
+* The headers sent with the response (Content-Type, Connection, etc).
+* The duration (a timeout is required for long-polling, but not all the others)
+* The "framing" of the messages. For multipart, each message has to be accompanied by a delimiter (boundary).
+* Random quirks (IE requires a certain number of dummy bytes at the beginning of the document streamed through the iframe).
+
+All these techniques try to minimize the latency of the **incoming** data from the server, but normal XMLHTTPRequest have to be used to **send** data from the client. Which brings us to the most optimal solution available today.
+
+## One transport to rule them all
+
+Meet WebSocket, an effort "to provide a mechanism for browser-based applications that need two-way communication with servers that does not rely on opening multiple HTTP connections", as the author Ian Hickson [puts it](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76).
+
+WebSocket takes advantage of the Upgrade header of the HTTP/1.1 specification, which means it's essentially a new protocol for communication:
+
+ The Upgrade general-header allows the client to specify what additional communication protocols it supports and would like to use if the server finds it appropriate to switch protocols.
+ Examples: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+
+WebSocket won't close the connection after sending a message or receiving one. It's essentially "TCP for the web", but with a security model built in the protocol, a fairly rare framing system and UTF-8 encoding (no binary).
+
+If we choose to implement it, some of the problems stated above still hold true:
+
+* The server has to give special treatment to the WebSocket requests, performing a handshake unique to the WebSocket protocol, and implement its new security system.
+* WebSocket is only supported by the most cutting-edge browser engines on the Desktop, a minority of the web population.
+
+## Node.JS koolaid to the rescue
+
+Node.JS presents developers with a truly unique and exciting possibility: rolling your own scalable non-blocking HTTP server in one of the most popular dynamic scripting languages of all time, with a simplistic API.
+
+Writing a (dummy) long-polling server is as easy as:
+
+<websockets-socketio/longpolling.js>
+
+Even with this simple API, consolidating your app logic in a way that works across all transports can be difficult.
+
+`Socket.IO-node` is here to help: Here's a chat server in 10 lines of code that also announces (broadcasts) who connects to the server:
+
+<websockets-socketio/broadcasts.js>
+
+And the best part is, under the hood, it'll handle your WebSocket, WebSocket over Flash, long polling, multipart and iframe connections. Sweet, isn't it?
+
+## Further reading
+
+If you want to learn more about Socket.IO, be sure to watch the [git](http://github.com/learnboost/socket.io) [repositories](http://github.com/learnboost/socket.io-node), and check out some of the projects people have created with it:
+
+* <http://github.com/substack/dnode>
+* <http://github.com/jacobthornton/space-tweet>
+* <http://github.com/mkilling/alfamegle>
+* <http://github.com/deserat/sock-drawer/>
+* <http://github.com/gerad/lazeroids-node/>
+
View
16 articles/websockets-socketio/broadcasts.js
@@ -0,0 +1,16 @@
+var buffer = [];
+io.on('connection', function(client){
+ client.send({ buffer: buffer });
+ client.broadcast({ announcement: client.sessionId + ' connected' });
+
+ client.on('message', function(message){
+ var msg = { message: [client.sessionId, message] };
+ buffer.push(msg);
+ if (buffer.length > 15) buffer.shift();
+ client.broadcast(msg);
+ });
+
+ client.on('disconnect', function(){
+ client.broadcast({ announcement: client.sessionId + ' disconnected' });
+ });
+});
View
6 articles/websockets-socketio/longpolling.js
@@ -0,0 +1,6 @@
+http.createServer(function (request, response) {
+ setTimeout(function(){
+ response.writeHead(200, {'Content-Type': 'text/plain'});
+ response.end('Hello World\n');
+ }, 20000);
+}).listen(8124);
View
8 articles/websockets-socketio/socketio-sample.js
@@ -0,0 +1,8 @@
+var socket = new io.Socket();
+socket.on('connect', function(){
+ // connected!
+});
+socket.on('message', function(msg){
+ // message coming
+});
+socket.send('Hello world!');
View
5 authors/Guillermo Rauch.markdown
@@ -0,0 +1,5 @@
+Email: rauchg@gmail.com
+Github: guille
+Twitter: rauchg
+
+I’m a Web specialist from Buenos Aires, Argentina (currently working and living in the SF Bay Area). I am acting as the CTO and co-founder of the education startup LearnBoost.
View
5 authors/Matt Ranney.markdown
@@ -0,0 +1,5 @@
+Github: mranney
+Email: mjr@ranney.com
+Homepage: http://mranney.com
+Twitter: mranney
+Location: Oakland, CA
View
5 authors/Phil Crosby.markdown
@@ -0,0 +1,5 @@
+Github: philc
+Email: phil.crosby@gmail.com
+Homepage: http://www.ninjahacking.com
+Twitter: philcrosby
+Location: Mountain View, CA
View
68 skin/article.haml
@@ -1,6 +1,36 @@
+#main.clearfix
+ :if article.version
+ %a.sha-version{ href: "/#{article.version}/#{article.name}" } Static Version
+ %h1
+ %a{href: "/" + article.name}&= article.title
+ = markdownEncode(article.markdown)
+ %hr{style:"clear:both"}
+ #disqus_thread.body
+ :script
+ var disqus_url = "http://howtonode.org/#{article.name}";
+ %script(src="http://disqus.com/forums/howtonodeorg/embed.js" type="text/javascript")
+ %a(href="http://disqus.com/forums/howtonodeorg/?url=ref")
+ View the discussion thread.
+ %a.dsq-brlink(href="http://disqus.com")
+ blog comments powered by
+ %span.logo-disqus
+ Disqus
+ :script
+ (function() {
+ var links = document.getElementsByTagName('a');
+ var query = '?';
+ for(var i = 0; i < links.length; i++) {
+ if(links[i].href.indexOf('#disqus_thread') >= 0) {
+ query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&amp;';
+ }
+ }
+ document.write('<script charset="utf-8" type="text/javascript" src="http://disqus.com/forums/howtonodeorg/get_num_replies.js' + query + '"></' + 'script>');
+ })();
+
+
#sidebar
.aside.clearfix
- %h3 About the Author
+ %h4 About the Author
%img.headshot{src: gravitar(author.email) }
%dl
%dt Name:
@@ -28,7 +58,7 @@
%a{href: author.homepage} Homepage
.aside
- %h3
+ %h4
About this Article
:if article.date
%dl{title: article.date}
@@ -85,7 +115,7 @@
:if article.related && article.related.length > 0
.bubble
- %h3
+ %h4
Other Articles by this Author
%ul
:each related in article.related
@@ -96,34 +126,4 @@
.bubble= markdownEncode(description)
%a.sponsor{href:"http://mediatemple.net/"}
- %img{src:"mtbadge.jpg", alt:"(mt) mediatemple", title:"Hosting donated by Media Temple"}
-
-
-#main.clearfix
- :if article.version
- %a.sha-version{ href: "/#{article.version}/#{article.name}" } Static Version
- %h1
- %a{href: "/" + article.name}&= article.title
- = markdownEncode(article.markdown)
- %hr{style:"clear:both"}
- #disqus_thread.body
- :script
- var disqus_url = "http://howtonode.org/#{article.name}";
- %script(src="http://disqus.com/forums/howtonodeorg/embed.js" type="text/javascript")
- %a(href="http://disqus.com/forums/howtonodeorg/?url=ref")
- View the discussion thread.
- %a.dsq-brlink(href="http://disqus.com")
- blog comments powered by
- %span.logo-disqus
- Disqus
- :script
- (function() {
- var links = document.getElementsByTagName('a');
- var query = '?';
- for(var i = 0; i < links.length; i++) {
- if(links[i].href.indexOf('#disqus_thread') >= 0) {
- query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&amp;';
- }
- }
- document.write('<script charset="utf-8" type="text/javascript" src="http://disqus.com/forums/howtonodeorg/get_num_replies.js' + query + '"></' + 'script>');
- })();
+ %img{src:"mtbadge.jpg", alt:"(mt) mediatemple", title:"Hosting donated by Media Temple"}
View
31 skin/index.haml
@@ -1,8 +1,21 @@
+#main.clearfix
+ :each article in articles.slice(0,15)
+ .article
+ .author
+ %img{src:gravitar(article.author.email, 30)}
+ By #{article.author.name}
+ .date{title: article.date}&= formatDate(article.date, "%Y.%m.%d")
+ %h3{style: "margin-right: 150px"}
+ %a{href: "/" + article.name}&= article.title
+ %p
+ = intro(article.markdown)
+ %a{href: "/" + article.name} Read more...
+
#sidebar
.aside
= markdownEncode(description)
.bubble
- %h3
+ %h4
Articles
%ul
:each article in articles.slice(0,20)
@@ -13,18 +26,4 @@
%span.versiontag&= article.node
.bubble= markdownEncode(description)
%a.sponsor{href:"http://mediatemple.net/"}
- %img{src:"mtbadge.jpg", alt:"(mt) mediatemple", title:"Hosting donated by Media Temple"}
-
-
-#main.clearfix
- :each article in articles.slice(0,15)
- .author
- %img{src:gravitar(article.author.email, 30)}
- By #{article.author.name}
- .date{title: article.date}&= formatDate(article.date, "%Y.%m.%d")
- %h1{style: "margin-right: 150px"}
- %a{href: "/" + article.name}&= article.title
- = intro(article.markdown)
- %a{href: "/" + article.name} Read more...
- %hr
-
+ %img{src:"mtbadge.jpg", alt:"(mt) mediatemple", title:"Hosting donated by Media Temple"}
View
15 skin/layout.haml
@@ -2,8 +2,9 @@
%html(lang="en" xmlns="http://www.w3.org/1999/xhtml")
%head
%meta(http-equiv="content-type" content="text/html; charset=UTF-8")
- %title #{(title ? title + " - " : "")}How To Node
- %link(href="/volcano.css" rel="stylesheet")
+ %title #{(title ? title + " - " : "")}How To Node - NodeJS
+ %meta(name="description" content="Learn the zen of coding in NodeJS.")
+ %link(href="/volcano.css?v2" rel="stylesheet")
%link(href="/print.css" rel="stylesheet" media="print")
%link(rel="alternate" type="application/rss+xml" title="RSS 2.0" href="/feed.xml")
%link(href="/favicon.ico" rel="icon" type="image/x-icon")
@@ -13,13 +14,13 @@
%a{href: "/"}
%h1
How To Node
- .tagline
+ %h2.tagline
The zen of coding in node.JS
= content
- #footer
- Site Design and code is &copy; 2010 to Tim Caswell under the MIT license. Content and articles are copyrighted to the individual authors. All code snippets used in the examples are in the public domain.
- %br
- Wheat running on node #{process.version}
+
+ #footer.clearfix
+ %p Site Design and code is &copy; 2010 to Tim Caswell under the MIT license. Content and articles are copyrighted to the individual authors. All code snippets used in the examples are in the public domain.
+ %p Wheat running on node #{process.version}
:script
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
View
5 skin/public/print.css
@@ -1,6 +1,7 @@
+body { background: none }
#container {max-width: 100%}
#header {display:none}
#sidebar {display: none}
-#main {margin: 0; padding: 0; -moz-box-shadow: none; border: none}
+#main {margin: 0; padding: 0; -moz-box-shadow: none; border: none; width: 99.9%}
#main pre code {max-height: none}
-#disqus_thread {display: none}
+#disqus_thread {display: none}
View
34 skin/public/volcano.css
@@ -52,7 +52,10 @@ body {
text-align: center;
color: #fff;
font-family: sans-serif;
- margin: 20px 0;
+ padding: 20px 0;
+}
+#footer.clearfix{
+ clear:both;
}
#main,
.aside,
@@ -63,11 +66,14 @@ body {
border: 1px solid rgba(255, 255, 255, 0.1);
}
#main {
+ float:left;
background-color: #fdfeff;
padding: 10px 30px;
- min-height: 400px;
- min-width: 400px;
- margin-right: 350px;
+ margin-right:0;
+ min-height: 300px;
+ min-width: 300px;
+ width: 65%;
+ /*margin-right: 350px;*/
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
@@ -189,9 +195,15 @@ body {
color: #444;
}
#main h3 {
+ font-size: 2em;
+ color: #222;
+}
+#main h4 {
color: #666;
}
-#main p {
+/* Added the default paragraph style to
+the list items to fix the size issue */
+#main p, #main li {
text-align: justify;
font-size: 14px;
line-height: 1.6em;
@@ -215,9 +227,15 @@ body {
#main .code-link .hash {
font-weight: normal;
}
+.article {
+ padding-bottom:10px;
+ margin-bottom:7px;
+ border-bottom:1px solid #ddd;
+}
#sidebar {
- float: right;
- width: 350px;
+ margin-left:0;
+ float: left;
+ width: 28%;
font-family: sans-serif;
font-size: 14px;
line-height: 1.6em;
@@ -225,7 +243,7 @@ body {
#sidebar dl {
margin: 10px 0;
}
-#sidebar h3 {
+#sidebar h4 {
font-size: 18px;
font-weight: bold;
line-height: 1em;
Please sign in to comment.
Something went wrong with that request. Please try again.