-
Notifications
You must be signed in to change notification settings - Fork 2
/
tutorial_part1.txt
370 lines (234 loc) · 16 KB
/
tutorial_part1.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
Introduction
After learning the erlang basics, and fell in love with it, one question pop up on my mind: Erlang is with no doubt a great language, but i make my living building web applications, so how i can join this 2 worlds?
After some searching i discover 3 Erlang Web Frameworks ( Nitrogen Project, ErlyWeb and ErlangWeb ) that try to do exactly this.
I tested the 3, and i decided to go with Nitrogen, it had cool examples, some documentation, and it look simple. I have spend the last 6 months building web applications using nitrogen and i could not be more pleased.
This tutorial is divided in 3 parts, in part 1 we will assume that you are a newbie to erlang also, so i will try to explain some basic stuff about erlang as we go, but i recommend you to read this hello world tutorials to better understand this one #link , #link ...
In part 2 we will show in a step by step explanation how to build a simple and powerful app with erlang and nitrogen, using twitter stream api.
In part 3 we will do a resume of the most important stuff we have talked and also present you some erlang tools that will help you mastering erlang.
Erlang On Nitro
->Read Erlang Tutorial, Warn about nitrogen state of development, tell how to install nitrogen
Nitrogen is open source project, started in November 2008 by @rklophaus under MIT-LICENSE.
Although its pretty stable, you should be ready to assume your risk using it on production environments.
All the following examples were made on Mac OS X but they should be valid to Linux and windows as well.
Have you erlang installed? Just open a terminal and type erl, if you have an error you need to install erlang (#link).
If you are seeing something like this
<code>
darkua:~ sergioveiga$ erl
Erlang R13B (erts-5.7.1) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.1 (abort with ^G)
1>
</code>
Congratulations you have already erlang installed :)
Now its time to install Nitrogen, what is very easy, just follow this instructions (#link)
#todo nitrogen instructions and create the first nitrogen project
Now that you have nitrogen installed time to start the party.
-> Explain OTP structure and nitrogen one, and suggest some changes.
Before we start its important to you to know, that erlang projects have a common structure by convection: (#todo check definitions)
-- ebin ()
-- *.beam (code compiled)
-- name_of_the_application.app (app definition)
-- include
-- *.hrl (structure descriptions to include)
-- src
-- *.erl (source code)
name_of_the_application.app is very important because it defines your application:
{application, name_of_the_application, [
{description, "App descritpion"},
{ vsn, "0.0.1" },
{mod, {name_of_the_application_app, []}},
{ applications, [ kernel, stdlib] },
{env, [
{platform, inets},
{port, 8000},
{session_timeout, 3600},
{sign_key, randomized},
{www_root, "./wwwroot"},
{templateroot,"./wwwroot/templates"},
{scratch_directory,'./wwwroot/scratch'}]},
]}.
This is an example of the app file, and you can read more about it here (#link). In resume its the file that describes you app to erlang vm, where you define its version, its environment vars, the modules, and the applications that your app will use.
Nitrogen as an extra dir called wwwroot, where we will store all our static files (js, html templates, css, images,etc...)
So now lets create your first project, using a nitrogen script that will do most of the work for you (#todo check how to link the stuff).
<code> nitrogen create twitter_stream</code>
A twitter_stream directory has been created and you should see a directory structure close to the one, i described before.
#todo changes
Now in order to see that everything is ok, just run in your shell the start script
<code> ./start.sh </code>
Now you should be able to go to http://localhost:8000/ and see you project running.
If you get this error
=ERROR REPORT==== 18-Nov-2009::19:25:31 ===
Failed initiating web server:
(...)
{listen,eaddrinuse}
(...)
This happens because you have already a server running on the same port. You need to shut down the other service, or change port on the .app file.
-> Explain what nitrogen does for you already (http://nitrogenproject.com/web/learn)
#todo Resume the website
-> Explain Nitrogen templates, and pages
Now that you have the project running, lets try to understand how nitrogen can help you.
Open twitter_stream.erl in the source directory
For now the most important in this page is understand the erlang behaviour concept. In resume Behaviours are formalizations of common patterns. So when we say -behavior(application), erlang expects this module to behave like an application. You can read more about it here. In this case the application behaviour just needs a start and a stop method. Now open start.sh script.
pre
echo Starting TwitterStream.
erl \
-name twitter_stream@localhost \
-pa ./ebin -pa ./include \
-s make all \
-eval "application:start(twitter_stream)"
/pre
In resume we are starting erlang vm, giving it a name, include and binary path, compiling the code, and start our application, that will execute the start function we saw on twitter_stream.erl. The rest for now its not important.
So what happens when you go to http:localhost:8000/ ?
If you check you twitter_stream.app file you will see this entries
{platform, inets},
{port, 8000},
This is very important because this defines what http server you are going to use, and in witch port is going to run. So nitrogen is not a web server, but it starts for you one of 3 possible web servers (inets, mochiweb, and yaws), you can read more about all of them, but for now we can use any. This also means that you dont need any other webserver (apache,etc...) to run your application.
So when you enter the address the web server will receive the request and match it to a page.
By default if you just call http://localhost:8000/ the request will be mapped to http://localhost:8000/web/index/ and this will run the web_index module inside /src/pages. So if you request /web/home it will execute the module web_home.erl inside the pages dir.
For now lets assume that this mapping /web/xpto -> web_xpto.erl happens by magic :)
So now open the file web_index file.
-module (web_index).
-include_lib ("nitrogen/include/wf.inc").
-compile(export_all).
main() ->
#template { file="./wwwroot/template.html"}.
title() ->
"web_index".
body() ->
#label{text="web_index body."}.
event(_) -> ok.
pre -module (web_index). /pre
The module definition is always required, and it defines your module name. Very important Reminder : erlang does not have namespaces, so try to use always prefixes on your own modules to avoid conflicts. For example dont call your module lists.erl... use for example my_lists.erl.. and dont laf this happens to the best :)
pre -include_lib ("nitrogen/include/wf.inc"). /pre
The second line is including a nitrogen lib, so you can use nitrogen stuff. Just keep it.
-compile(export_all)
In order to call function from a method from other module, you need to export the functions, to avoid that you can use this directive, but use it with caution, its best to export each one, like this:
pre -export([ main/0, body/0, title/0, event/0]). /pre
So module, include, and exports is what we can call the "header" of the module. Bellow is where the code really begins.
pre
main() ->
#template { file="./wwwroot/template.html"}.
/pre
This function is needed in all your pages because its the one that will be executed when you requests this page.
This function needs to return (in erlang return is always the last line of execution) a #template element. In erlang exists a structure called record, this record is the closest thing you have to objects, and in the end is just a sugar syntax for tuples. So this records are used by nitrogen to create a serie of elements that will help you build web application with erlang. The template element will define the html template for this page. The file atribute is the location of your html page that will be used as template.
Open wwwroot/template.html.
As you can see its a perfect normal html page, with some weird stuff [[[page:title()]]] on it. As you probably are imagining right now, nitrogen will parse this page, and will replace the [[[page:title()]]] with the output of the title function inside web_index. The reason to use page, and not web_index is that you can use a template for many pages, and this way it will execute the title of each calling page.
The other important part is the [[[script]]] element. This is where nitrogen will put all javascript he creates. Just don't remove it or you will get into troubles :)
Now back to web_index page, you can see that title function return a simple string, and body uses another nitrogen element. At this time i recommend you to go check all possible nitrogen elements (#link) so you can have an idea of what you can use. But in resume, you have an element for almost all html elements (div, li, table,input...). The only problem with this approach is that nitrogen does not use always the same tags, so for example a span is #span element, but a div is the #panel element... :( but dont worry after a while you get use to it :)
So lets try this, replace the body by this :
body()->
#textbox{text="some text"},
#button{text="Hello!"}.
now go to erlang shell you have started the project and execute sync:go().
(nitrogen@localhost)2> sync:go().
Recompile: ./src/pages/web_index
./src/pages/web_index.erl:12: Warning: a term is constructed, but never used
ok
sync:go() will turn into one of you best friends because it will compile all the files you have changed :)
You can see a warning, that is mainly telling that you have some code, that in never used, and if you go to the browser, and can see that you only have the button there. So what happend to the textbox? Like i told you before, erlang only returns the last code line, so mainly you are only returning the button. To return all you just need to do this:
body()->
[
#textbox{text="some text"},
#button{text="hello"}
].
Mainly you just need to add your elements to a list. Yep in erlang you dont have arrays you have lists. The diff? read here.
Now sync again and go to the browser. You should see now a input text element and a button :)
Nitrogen has also a set of built in functions that do a lot of stuff for you, on for example is the render one.
body()->
Elements = [
#textbox{id=input_id, text="some text"},
#button{text="hello"}
],
wf:render(Elements).
This does exactly the same as before but lets you organize your code, the way you like most, what is always great :)
So now that you now the alphabet lets start making words :)
-> Explain Nitrogen Actions
The interaction between user and application, nitrogen calls it actions. So lets add some actions to our previous button.
#button{text="hello",actions=#event{type=click,postback=hello}}
This will tell the button that on click, will execute the hello event.
So the next thing you need to do is the hello event
event(hello)->
io:format("~p~n",["test message"]);
event(_) -> ok.
sync the code, click on hello button and go back to your erlang shell.
You have just created your first client server interaction :). If you are using firebug you can easily see
that this is done using an AJAX Event, where all the page info is send to the server, and matched to the specific
event.
Now lets grab some info
event(hello)->
Input = wf:q("input_id"),
io:format("~p~n",[Input]);
<code>
(nitrogen@localhost)1> ["some text"]
</code>
As you can see using nitrogen wf:q(id) function you have access to input box value. In resume you can access to all parameters sent in a get or post request.
Lets test get parameters. Change your body function to this:
<code>
body()->
Input = wf:q("input_id"),
io:format("~p~n",[Input]),
Elements = [
#textbox{id=input_id, text="some text"},
#button{text="hello",actions=#event{type=click,postback=hello}}
],
wf:render(Elements).
</code>
Dont forget to compile your changes, by running sync:go(), and now change url to http://localhost:8000/?input_id=hello
As you can see it work exactly the same, the important thing to remember is that wf:q can only read the header parameters of the request,
so for example if now you click on the button, you are creating another request, and input_id=hello from the previous one does not exist.
Another great stuff about wf:q it lets you redefine GET or POST parameters, without overwrite the values, for example if you request this, http://localhost:8000/?input_id=hello&input_id=world you will get
(nitrogen@localhost)9> ["hello","world"]
and although this being great, it creates another problem. wf:q always returns a list, so you need to get the value or values out of the list, and in 99% of the times the list will have only one element, so you can use this :
<code> hd(wf:q(input_id)) </code>
This function will get the head of the list for you. But be careful because when the parameter does not exist you will get into trouble. For example add this to body function top
<code>hd(wf:q("input_id"))</code>
and now call the old url http://localhost:8000/
Hehe, say hello to your first of many erlang errors :)
Go back to the shell, and you will see exactly what happened
<code>
error-badarg
[{erlang,hd,[[]]},
{web_index,body,0},
{element_template,eval_callbacks,2},
{element_template,eval,2},
{element_template,eval,2},
{element_template,eval,2},
{element_template,eval,2},
{element_template,eval,2}]
</code>
Here you have info of witch module and function create the error {web_index,body,0}, and also witch instruction erlang,hd,[[]], and in resume
the problem is that you are trying to get the head of an empty list.
In order to solve this we will introduce on of the most common structures in a erlang code, the case statement. As you probably have heard erlang does not have if statement, its false it has, but in the end you never need to use it, because the case really solves all your problems.
So lets change the code to this:
<code>
Input = case wf:q("input_id") of
[]->
"Oops, not input!";
_->
hd(wf:q("input_id"))
end,
(...)
</code>
Case works like it is supposed to be, it will evaluate the return of wf:q and if it evaluates to [], what happens when you dont have the parameter defined, it will associate the string "Oops, not input!" to Input, or if it is something else (_) does not matter what, it will grab the head of the list.
Since erlang lives a lot of pattern matching, _ option is a powerful arm for your arsenal :)
Import reminder, and one of the most annoying stuff around erlang, is that the way you end your instruction (, or ; or .) its not a coincidence.
In resume, the end of a function is indicated by the dot (.), but if your function uses pattern matching with the same number of arguments, like it happens on event, only the last one ends with the (.) all the others end with (;).
<code>
event(hello)->
Input = hd(wf:q("input_id")),
io:format("~p~n",[Input]);
event(_) -> ok.
</code>
All other instruction always end with comma (,) with exception of case instruction where the different options are ended also with (;) and the last one does not need any, like you have seen in the previous example.
Back to the action itself, we were just sending an Atom (erlang simple structure, any word starting with a lower_case :) ), but we can send info in the postback
replace
<code>postback=hello</code>
by
<code>postback={hello,"Hello, i'm string inside a atoom"}</code>
and adapt your event to receive the new info
<code>
event({hello,Item})->
io:format("~p~n",[Item]);
</code>
Compile and test, and as you can see its very easy to pass data inside events.
Now that you have understood the basics around nitrogen and erlang its time to check nitrogen complex elements like for example the comet element.
So lets jump to part 2 of this tutorial, where we will build in a step-by-step explanation a simple twitter client.