Skip to content
Browse files

Added basic HTTP server + client recipes.

  • Loading branch information...
1 parent c2636cc commit 3b82a24b7285f0d9f0d55b3fb9d482aa98fae450 @joaomoreno joaomoreno committed
Showing with 335 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +1 −0 authors.textile
  3. +63 −0 chapters/networking/basic-http-client.textile
  4. +269 −0 chapters/networking/basic-http-server.textile
View
2 .gitignore
@@ -3,3 +3,5 @@ _site
*.swp
*.swo
+
+.DS_Store
View
1 authors.textile
@@ -14,6 +14,7 @@ _The following people are totally rad and awesome because they have contributed
* Sebastian Slomski _sebastian@simple-systems.org_
* Aaron Weinberger _aw9994@cs.ship.edu_
* James C. Holder _cs_cookbook@thirdtruck.org_
+* João Moreno _coffeecb @joaomoreno.com_
* ...You! What are you waiting for? Check out the <a href="/contributing">contributing</a> section and get cracking!
View
63 chapters/networking/basic-http-client.textile
@@ -0,0 +1,63 @@
+---
+layout: recipe
+title: Basic HTTP Client
+chapter: Networking
+---
+
+h2. Problem
+
+You want to create a HTTP client.
+
+h2. Solution
+
+In this recipe, we'll use "node.js":http://nodejs.org/ 's HTTP library. We'll go from a simple GET request example to a client which returns the external IP of a computer.
+
+h3. GET something
+
+{% highlight coffeescript %}
+http = require 'http'
+
+http.get { host: 'www.google.com' }, (res) ->
+ console.log res.statusCode
+{% endhighlight %}
+
+The @get@ function, from node.js's @http@ module, issues a GET request to a HTTP server. The response comes in the form of a callback, which we can handle in a function. This example merely prints the response status code. Check it out:
+
+{% highlight console %}
+$ coffee http-client.coffee
+200
+
+{% endhighlight %}
+
+h3. What's my IP?
+
+If you are inside a network which relies on "NAT":http://en.wikipedia.org/wiki/Network_address_translation such as a LAN, you probably have faced the issue of finding out what's your external IP address. Let's write a small coffeescript for this.
+
+{% highlight coffeescript %}
+http = require 'http'
+
+http.get { host: 'checkip.dyndns.org' }, (res) ->
+ data = ''
+ res.on 'data', (chunk) ->
+ data += chunk.toString()
+ res.on 'end', () ->
+ console.log data.match(/([0-9]+\.){3}[0-9]+/)[0]
+{% endhighlight %}
+
+We can get the data from the result object by listening on its @'data'@ event; and know that it has come to an end once the @'end'@ event has been fired. When that happens, we can do a simple regular expression match to extract our IP address. Try it:
+
+{% highlight console %}
+$ coffee http-client.coffee
+123.123.123.123
+{% endhighlight %}
+
+h2. Discussion
+
+Note that @http.get@ is a shortcut of @http.request@. The latter allows you to issue HTTP requests with different methods, such as POST or PUT.
+
+For API and overall information on this subject, check node.js's "http":http://nodejs.org/docs/latest/api/http.html and "https":http://nodejs.org/docs/latest/api/https.html documentation pages. Also, the "HTTP spec":http://www.ietf.org/rfc/rfc2616.txt might come in handy.
+
+h3. Exercises
+
+* Create a client for the key-value store HTTP server, from the "Basic HTTP Server":http://coffeescriptcookbook.com/chapters/networking/basic-http-server recipe.
+
View
269 chapters/networking/basic-http-server.textile
@@ -0,0 +1,269 @@
+---
+layout: recipe
+title: Basic HTTP Server
+chapter: Networking
+---
+
+h2. Problem
+
+You want to create a HTTP server over a network. Over the course of this recipe, we'll go step by step from the smallest server possible to a functional key-value store.
+
+h2. Solution
+
+We'll use "node.js":http://nodejs.org/ 's HTTP library to our own selfish purposes and create the simplest web server possible in Coffeescript.
+
+h3. Say 'hi\n'
+
+We can start by importing the @http@ module. This module has a nice helper function &mdash; @createServer@ &mdash; which, given a simple request handler, creates a HTTP server. All that's left to do then is have the server listening on a port.
+
+{% highlight coffeescript %}
+http = require 'http'
+server = http.createServer (req, res) -> res.end 'hi\n'
+server.listen 8000
+{% endhighlight %}
+
+To run this example, simply put in a file and run it. You can kill it with @Ctrl-C@. We can test it using the @curl@ command, available on most *nix platforms:
+
+{% highlight console %}
+$ curl -D - http://localhost:8000/
+HTTP/1.1 200 OK
+Connection: keep-alive
+Transfer-Encoding: chunked
+
+hi
+{% endhighlight %}
+
+h3. What's going on?
+
+Let's get a little bit more feedback on what's happening on our server. While we're at it, we could also be friendlier to our clients and provide them some HTTP headers.
+
+{% highlight coffeescript %}
+http = require 'http'
+
+server = http.createServer (req, res) ->
+ console.log req.method, req.url
+ data = 'hi\n'
+ res.writeHead 200,
+ 'Content-Type': 'text/plain'
+ 'Content-Length': data.length
+ res.end data
+
+server.listen 8000
+{% endhighlight %}
+
+Try to access it once again, but this time use different URL paths, such as @http://localhost:8000/coffee@. You'll see something like this on the server console:
+
+{% highlight console %}
+$ coffee http-server.coffee
+GET /
+GET /coffee
+GET /user/1337
+{% endhighlight %}
+
+h3. GETting stuff
+
+What if our webserver was able to hold some data? We'll try to come up with a simple key-value store in which elements are retrievable via GET requests. Provide a key on the request path and the server will return the corresponding value - or 404 if it doesn't exist.
+
+{% highlight coffeescript %}
+http = require 'http'
+
+store = # we'll use a simple object as our store
+ foo: 'bar'
+ coffee: 'script'
+
+server = http.createServer (req, res) ->
+ console.log req.method, req.url
+
+ value = store[req.url[1..]]
+
+ if not value
+ res.writeHead 404
+ else
+ res.writeHead 200,
+ 'Content-Type': 'text/plain'
+ 'Content-Length': value.length + 1
+ res.write value + '\n'
+
+ res.end()
+
+server.listen 8000
+{% endhighlight %}
+
+{% highlight console %}
+$ curl -D - http://localhost:8000/coffee
+HTTP/1.1 200 OK
+Content-Type: text/plain
+Content-Length: 7
+Connection: keep-alive
+
+script
+
+$ curl -D - http://localhost:8000/oops
+HTTP/1.1 404 Not Found
+Connection: keep-alive
+Transfer-Encoding: chunked
+
+{% endhighlight %}
+
+h3. Use your head(ers)
+
+Let's face it, @text/plain@ is kind of lame. How about if we use something hip like @application/json@ or @text/xml@? Also, our store retrieval process could use a bit of refactoring &mdash; how about some exception throwing &amp; handling? Let's see what we can come up with:
+
+{% highlight coffeescript %}
+http = require 'http'
+
+# known mime types
+[any, json, xml] = ['*/*', 'application/json', 'text/xml']
+
+# gets a value from the db in format [value, contentType]
+get = (store, key, format) ->
+ value = store[key]
+ throw 'Unknown key' if not value
+ switch format
+ when any, json then [JSON.stringify({ key: key, value: value }), json]
+ when xml then ["<key>#{ key }</key>\n<value>#{ value }</value>", xml]
+ else throw 'Unknown format'
+
+store =
+ foo: 'bar'
+ coffee: 'script'
+
+server = http.createServer (req, res) ->
+ console.log req.method, req.url
+
+ try
+ key = req.url[1..]
+ [value, contentType] = get store, key, req.headers.accept
+ code = 200
+ catch error
+ contentType = 'text/plain'
+ value = error
+ code = 404
+
+ res.writeHead code,
+ 'Content-Type': contentType
+ 'Content-Length': value.length + 1
+ res.write value + '\n'
+ res.end()
+
+server.listen 8000
+{% endhighlight %}
+
+This server will still return the value which matches a given key, or 404 if non-existent. But it will structure the response either in JSON or XML, according to the @Accept@ header. See for yourself:
+
+{% highlight console %}
+$ curl http://localhost:8000/
+Unknown key
+
+$ curl http://localhost:8000/coffee
+{"key":"coffee","value":"script"}
+
+$ curl -H "Accept: text/xml" http://localhost:8000/coffee
+<key>coffee</key>
+<value>script</value>
+
+$ curl -H "Accept: image/png" http://localhost:8000/coffee
+Unknown format
+{% endhighlight %}
+
+h3. You gotta give to get back
+
+The obvious last step in our adventure is to provide the client the ability to store data. We'll keep our RESTiness by listening to POST requests for this purpose.
+
+{% highlight coffeescript %}
+http = require 'http'
+
+# known mime types
+[any, json, xml] = ['*/*', 'application/json', 'text/xml']
+
+# gets a value from the db in format [value, contentType]
+get = (store, key, format) ->
+ value = store[key]
+ throw 'Unknown key' if not value
+ switch format
+ when any, json then [JSON.stringify({ key: key, value: value }), json]
+ when xml then ["<key>#{ key }</key>\n<value>#{ value }</value>", xml]
+ else throw 'Unknown format'
+
+# puts a value in the db
+put = (store, key, value) ->
+ throw 'Invalid key' if not key or key is ''
+ store[key] = value
+
+store =
+ foo: 'bar'
+ coffee: 'script'
+
+# helper function that responds to the client
+respond = (res, code, contentType, data) ->
+ res.writeHead code,
+ 'Content-Type': contentType
+ 'Content-Length': data.length
+ res.write data
+ res.end()
+
+server = http.createServer (req, res) ->
+ console.log req.method, req.url
+ key = req.url[1..]
+ contentType = 'text/plain'
+ code = 404
+
+ switch req.method
+ when 'GET'
+ try
+ [value, contentType] = get store, key, req.headers.accept
+ code = 200
+ catch error
+ value = error
+ respond res, code, contentType, value + '\n'
+
+ when 'POST'
+ value = ''
+ req.on 'data', (chunk) -> value += chunk
+ req.on 'end', () ->
+ try
+ put store, key, value
+ value = ''
+ code = 200
+ catch error
+ value = error + '\n'
+ respond res, code, contentType, value
+
+server.listen 8000
+{% endhighlight %}
+
+Notice how the data is received in a POST request. By attaching some handlers on the @'data'@ and @'end'@ events of the request object, we're able to buffer and finally save the data from the client in the @store@.
+
+{% highlight console %}
+$ curl -D - http://localhost:8000/cookie
+HTTP/1.1 404 Not Found # ...
+Unknown key
+
+$ curl -D - -d "monster" http://localhost:8000/cookie
+HTTP/1.1 200 OK # ...
+
+$ curl -D - http://localhost:8000/cookie
+HTTP/1.1 200 OK # ...
+{"key":"cookie","value":"monster"}
+{% endhighlight %}
+
+h2. Discussion
+
+Give @http.createServer@ a function in the shape of @(request, response) -> ...@ and it will return a server object, which we can use to listen on a port. Interact with the @request@ and @response@ objects to give the server its behaviour. Listen on port 8000 using @server.listen 8000@.
+
+For API and overall information on this subject, check node.js's "http":http://nodejs.org/docs/latest/api/http.html and "https":http://nodejs.org/docs/latest/api/https.html documentation pages. Also, the "HTTP spec":http://www.ietf.org/rfc/rfc2616.txt might come in handy.
+
+h3. Exercises
+
+* Create a layer in between the server and the developer which would allow the developer to do something like:
+
+{% highlight coffeescript %}
+server = layer.createServer
+ 'GET /': (req, res) ->
+ ...
+ 'GET /page': (req, res) ->
+ ...
+ 'PUT /image': (req, res) ->
+ ...
+{% endhighlight %}
+

0 comments on commit 3b82a24

Please sign in to comment.
Something went wrong with that request. Please try again.