Free Trial

Safari Books Online is a digital library providing on-demand subscription access to thousands of learning resources.


  • Create BookmarkCreate Bookmark
  • Create Note or TagCreate Note or Tag
  • DownloadDownload
  • PrintPrint
Share this Page URL
Help

4. Implementing REST > A Full Example

A Full Example

So far this chapter has used little bits of code to show how to do different parts of a service. This section will take those bits and unify them into a complete service that can be used as a basis for your own applications. Most (but not all) of the code here is from the previous sections.

In general, a REST service will want to do one of two things: either work records in Mnesia or another data store, or interact with some form of backend application by sending messages back and forth. Here is a full example using Mnesia.

In this example, when a GET event comes in, it will query Mnesia, return a list of all the airports, and return them to the user.

When the user sends a POST request, the system will add a new record to the Mnesia data store. If needed we could also take other actions here, such as invalidating a cache or calling other functions to take other actions.

When the user sends a PUT request, we will update an existing record. In this case we will look it up by its IATA code and update the airport for new information. We cannot handle the case where an airport changes its IATA code, but this should be rare enough a case that we could delete the record and create it again.

When the user sends a DELETE request, we will delete the record from the data store.

There is also an extra clause at the end to catch any requests that are not one of the four major HTTP requests and return a “405 Method Not Allowed” response.

In order for all this to work, we need to have an airport data format; in this case it is very simple and shown in Example 4-2. This record includes only the airport IATA code, name, city, and country.

We must also set up a table in the Mnesia data store, as in Example 4-13. This must be done before the code is run and normally would be done in an .erlang file that Yaws will run on startup.

Warning

The calls to io:format serialize all server activity through the IO server; remove them for production.

Example 4-13. Setting up Mnesia

%% Add this to the .erlang file
application:start(mnesia).
mnesia:create_table(airport, 
		    [
		     {attributes,record_info(fields, airport)},
		     {index, [country]}]).

Example 4-14 brings all of the airport example code together.

Example 4-14. Full airport example

-module(rest).
-include("/usr/lib/erlang/lib/stdlib-1.17.3/include/qlc.hrl").
-include("/usr/lib/yaws/include/yaws_api.hrl").
-export([out/1, addAirport/4, handle/2]).
%-compile(export_all).


-define(RECORD_TYPE,      airport).
-define(RECORD_KEY_FIELD, code).

-record(?RECORD_TYPE,
        {?RECORD_KEY_FIELD, city, country, name }).

out(Arg) ->
    Method = method(Arg) ,
    io:format("~p:~p ~p Request ~n", [?MODULE, ?LINE, Method]),
    handle(Method, Arg).

method(Arg) ->
  Rec = Arg#arg.req,
  Rec#http_request.method.


convert_to_json(Lines) ->
    Data = [{obj, 
	     [{airport, Line#?RECORD_TYPE.code},
	      {city,    Line#?RECORD_TYPE.city},
	      {country, Line#?RECORD_TYPE.country},
	      {name,    Line#?RECORD_TYPE.name}]}
	    || Line <- Lines],
    JsonData = {obj, [{data, Data}]},
    rfc4627:encode(JsonData).

addAirport(Code, City, Country, Name) ->
    NewRec = #?RECORD_TYPE{ 
		 ?RECORD_KEY_FIELD	= Code,
		 city			= City,
		 country		= Country,
		 name			= Name},
    io:format("~p:~p Adding Airport ~p~n",
	      [?MODULE,?LINE, NewRec]),
    Add = fun() ->
                         mnesia:write(NewRec)
                 end,
    {atomic, _Rec} = mnesia:transaction(Add),
    NewRec.


handle('GET', _Arg) ->
    io:format("~n ~p:~p GET Request ~n", [?MODULE, ?LINE]),
    Records = do(qlc:q([X || X <- mnesia:table(?RECORD_TYPE)])),
    Json = convert_to_json( Records),
    io:format("~n ~p:~p GET Request Response ~p ~n", [?MODULE, ?LINE, Json]),
    {html, Json};

handle('POST', Arg) ->
    {ok, Json, _} = rfc4627:decode(Arg#arg.clidata),
    io:format("~n~p:~p POST request ~p~n", 
              [?MODULE, ?LINE, Json]),
    Airport	= rfc4627:get_field(Json, "airport", <<>>),
    City	= rfc4627:get_field(Json, "city", <<>>),
    Country	= rfc4627:get_field(Json, "country", <<>>),
    Name	= rfc4627:get_field(Json, "name", <<>>),
    _Status = addAirport(Airport, City, Country, Name),
    [{status, 201},
     {html, Arg#arg.clidata}];



handle('PUT', Arg) ->
    [IndexValue,_] = string:tokens(Arg#arg.pathinfo),    
    {ok, Json, _} = rfc4627:decode(Arg#arg.clidata),
    io:format("~p:~p PUT request ~p ~p~n",
              [?MODULE, ?LINE, IndexValue, Json]),
    Airport	= rfc4627:get_field(Json, "airport", <<>>),
    City	= rfc4627:get_field(Json, "city", <<>>),
    Country	= rfc4627:get_field(Json, "country", <<>>),
    Name	= rfc4627:get_field(Json, "name", <<>>),

    NewRec = #?RECORD_TYPE{
		 ?RECORD_KEY_FIELD	= Airport,
		 city			= City,
		 country		= Country,
		 name			= Name},

    io:format("~p:~p Renaming ~p", 
              [?MODULE, ?LINE, NewRec]),
    ChangeName = fun() ->
			 mnesia:delete(
			   {?RECORD_KEY_FIELD, IndexValue}),			     
                         mnesia:write(NewRec)
                 end,
    {atomic, _Rec} = mnesia:transaction(ChangeName),
    [{status, 200},
     {html, IndexValue}];


handle('DELETE', Arg) ->

    [IndexValue, _ ] = string:tokens(Arg#arg.pathinfo),    
    io:format("~p:~p DELETE request ~p",
              [?MODULE, ?LINE, IndexValue]),

    Delete = fun() ->
                     mnesia:delete(
                       {?RECORD_KEY_FIELD, IndexValue})
             end,

    Resp = mnesia:transaction(Delete),
    case Resp of
        {atomic, ok} ->
            [{status, 204}];
        {_, Error} ->
            io:format("~p:~p Error ~p ", 
                      [?MODULE, ?LINE, Error]),
            [{status, 400},
             {html, Error}]
    end;


handle(Method,_) ->
    [{error, "Unknown method " ++ Method},
     {status, 405},
     {header, "Allow: GET, HEAD, POST, PUT, DELETE"}
     ].


do(Q)->
    F = fun() ->
                qlc:e(Q) 
	end,
    {atomic, Value} = mnesia:transaction(F),
    Value.

Finally, we need a frontend to use all this with. I created a simple frontend in CoffeeScript with ExtJS (see http://sencha.com) and it is included in Example 4-15. This creates a UI in the browser that looks like Figure 4-1.

Example 4-15. CoffeeScript frontend (airport.coffee)

makeModel = ->
        Ext.define("Airport",
                extend: "Ext.data.Model",
                fields:[
                        {name: "airport"}
                        {name: "city"}
                        {name: "country"}
                        {name: "name"}
                        ]
        )

makeStore = ->
        model = makeModel()
        store = Ext.create("Ext.data.Store",
                autoLoad : true
                autoSync : true
                model    : model
                proxy    :
                        type   : "rest"
                        url    : "airports.yaws" # Will need to change the backend here
                        reader :
                                type: "json"
                                root: "data"
                        writer:
                                type: "json"
        )

setupAirports = ->
        store      = makeStore()
        rowEditing = Ext.create "Ext.grid.plugin.RowEditing"
        grid       = Ext.create "Ext.grid.Panel"
                renderTo : document.body
                plugins  : [rowEditing]
                width    : 500
                height   : 300
                title    : "Airports"
                store    : store
                columns:
                        [
                                {
                                        text      : 'Airport',
                                        width     : 60
                                        sortable  : true
                                        dataIndex : "airport"
                                        editor    : {allowBlank: false}
                                }
                                {
                                        text      : "City"
                                        dataIndex : "city"
                                        sortable  : true
                                        editor    : {allowBlank: false}
                                }
                                {
                                        text      : "Country"
                                        dataIndex : "country"
                                        sortable  : true
                                        editor    : {allowBlank: false}
                                }
                                {
                                        text      : "Airport Name"
                                        dataIndex : "name"
                                        sortable  : true
                                        editor    : {allowBlank: false}
                                }
                        ]
                dockedItems:
                        [
                                xtype: "toolbar"
                                items:
                                        [
                                                {
                                                        text: "Add"
                                                        handler: ->
                                                                store.insert(0, new Airport())
                                                                rowEditing.startEdit(0,0)
                                                }
                                                {
                                                        itemId: 'delete'
                                                        text: "Delete"
                                                        handler:  () ->
                                                                selection = grid
                                                                        .getView()
                                                                        .getSelectionModel()
                                                                        .getSelection()[0]
                                                                if(selection)
                                                                        store.remove(selection)
                                                }
                                        ]
                        ]

Ext.onReady setupAirports
Airports UI in a browser

Figure 4-1. Airports UI in a browser



[8] For those who have worked in .NET, this is similar to LINQ.