e1401d8e383baa19259dc3254b98ad96d458c887
[src/app-framework-binder.git] / docs / afb-binding-writing.md
1 # Overview of the bindings
2
3 The ***binder*** serves files through HTTP protocol and offers developers the capability to offer application API methods through HTTP or
4 WebSocket protocol.
5
6 The ***bindings*** are used to add **API** to ***binders***.  
7 This part describes how to write a ***binding*** for ***binder***
8 or in other words how to add a new **API** to the system.
9
10 This section target developers.
11
12 This section shortly explain how to write a binding
13 using the C programming language.
14
15 It is convenient to install the ***binder*** on the
16 desktop used for writing the binding.  
17 It allows for easy debug and test.
18
19 ## Nature of a binding
20
21 A ***binding*** is an independent piece of software compiled as a shared
22 library and dynamically loaded by a ***binder***.  
23 It is intended to provide one **API** (**A**pplication **P**rogramming
24 **I**nterface).
25
26 The **API** is designated and accessed through its name.  
27 It contains several **verbs** that implement the ***binding***
28 functionalities.  
29 Each of these **verbs** is a **method** that
30 processes requests of applications and sends results.
31
32 The ***binding***'s methods are invoked by HTTP or websocket
33 requests.
34
35 The **methods** of the ***bindings*** are noted **api/verb**
36 where **api** is the **API** name of the binding and **verb** is
37 the **method**'s name within the **API**.  
38 This notation comes from HTTP invocations that rely on URL path terminated
39 with **api/verb**.
40
41 The name of an **API** can be made of any characters except:
42
43 - the control characters (\u0000 .. \u001f)
44 - the characters of the set { ' ', '"', '#', '%', '&',
45    '\'', '/', '?', '`', '\x7f' }
46
47 The names of the **verbs** can be any character.
48
49 The binder makes no distinctions between upper case and lower case
50 latin letters.  
51 So **API/VERB** matches **Api/Verb** or **api/verb**.
52
53 ## Versions of the bindings
54
55 Since introduction of the binder, the way how bindings are written
56 evolved a little. While changing, attention was made to ensure binary
57 compatibility between the different versions.
58
59 Actually it exists 3 ways of writing ***bindings***.
60 You can either write:
61
62 - a binding version 1 (not more supported);
63 - a binding version 2 (not recommended);
64 - a binding version 3 (RECOMMENDED).
65
66 A ***binder*** loads and runs any of these version in any combination.  
67 This document explain how to write bindings version 3.
68
69 <!-- pagebreak -->
70
71 ## Sample binding: tuto-1
72
73 This is the code of the binding **tuto-1.c**:
74
75 ```C
76   1 #define AFB_BINDING_VERSION 3
77   2 #include <afb/afb-binding.h>
78   3
79   4 void hello(afb_req_t req)
80   5 {
81   6         AFB_REQ_DEBUG(req, "hello world");
82   7         afb_req_reply(req, NULL, NULL, "hello world");
83   8 }
84   9
85  10 const afb_verb_t verbs[] = {
86  11         { .verb="hello", .callback=hello },
87  12         { .verb=NULL }
88  13 };
89  14
90  15 const afb_binding_t afbBindingExport = {
91  16         .api = "tuto-1",
92  17         .verbs = verbs
93  18 };
94 ```
95
96 Compiling:
97
98 ```bash
99 gcc -fPIC -shared tuto-1.c -o tuto-1.so $(pkg-config --cflags-only-I afb-daemon)
100 ```
101
102 > Note: the variable environment variable PKG_CONFIG_PATH might be necessary
103 > tuned to get **pkg-config** working properly
104
105 Running:
106
107 ```bash
108 afb-daemon --binding tuto-1.so --port 3333 --token ''
109 ```
110
111 At this point, afb-daemon has started, it loaded the binding tuto-1.so and now
112 listen at localhost on the port 3333.
113
114 Testing using **curl**:
115
116 ```bash
117 $ curl http://localhost:3333/api/tuto-1/hello
118 {"jtype":"afb-reply","request":{"status":"success","info":"hello world","uuid":"1e587b54-900b-49ab-9940-46141bc2e1d6"}}
119 ```
120
121 Testing using **afb-client-demo** (with option -H for
122 getting a human readable output):
123
124 ```bash
125 $ afb-client-demo -H ws://localhost:3333/api?token=x tuto-1 hello
126 ON-REPLY 1:tuto-1/hello: OK
127 {
128   "jtype":"afb-reply",
129   "request":{
130     "status":"success",
131     "info":"hello world",
132     "uuid":"03a84ad1-458a-4ace-af74-b1da917391b9"
133   }
134 }
135 ```
136
137 This shows basic things:
138
139 - The include to get for creating a binding
140 - How to declare the API offered by the binding
141 - How to handle requests made to the binding
142
143 ### Getting declarations for the binding
144
145 The lines 1 and 2 show how to get the include file **afb-binding.h**.
146
147 ```C
148   1 #define AFB_BINDING_VERSION 3
149   2 #include <afb/afb-binding.h>
150 ```
151
152 You must define the version of ***binding*** that you are using. 
153 This is done line 1 where we define that this is the version 3 (earlier
154 versions 1 and 2 are deprecated).
155
156 If you don't define it, an error is reported and the compilation aborts.
157
158 To include **afb-binding.h** successfully, the include search path
159 should be set correctly if needed (not needed only if installed in
160 /usr/include/afb directory that is the default).
161
162 Setting the include path is easy using **pkg-config**:
163
164 ```bash
165 pkg-config --cflags-only-I afb-daemon
166 ```
167
168 > Note for **C++** developers: 
169 >
170 > The ***binder*** currently expose a draft version of **C++** api.  
171 > To get it include the file <**afb/afb-binding**> (without **.h**).
172
173
174 ### Declaring the API of the binding
175
176 Lines 10 to 18 show the declaration of the ***binding***.
177
178 The ***binder*** knows that this is a ***binding*** because
179 it finds the exported symbol **afbBindingExport** that is expected to be
180 a structure of type **afb_binding_t**.
181
182 ```C
183  10 const afb_verb_t verbs[] = {
184  11         { .verb="hello", .callback=hello },
185  12         { .verb=NULL }
186  13 };
187  14
188  15 const afb_binding_t afbBindingExport = {
189  16         .api = "tuto-1",
190  17         .verbs = verbs
191  18 };
192 ```
193
194 The structure **afbBindingExport** actually tells that:
195
196 - the exported **API** name is **tuto-1** (line 16)
197 - the array of verbs is the above defined one
198
199 The exported list of verb is specified by an array of structures of
200 type **afb_verb_t**, each describing a verb, ended with a verb NULL (line 12).
201
202 The only defined verb here (line 11) is named **hello** (field **.verb**)
203 and the function that handle the related request is **hello**
204 (field **.callback**).
205
206 ### Handling binder's requests
207
208 As shown above this is by default the common include directory where
209 the AGL stuff is installed.
210
211 ```C
212   4 void hello(afb_req_t req)
213   5 {
214   6         AFB_REQ_DEBUG(req, "hello world");
215   7         afb_req_reply(req, NULL, NULL, "hello world");
216   8 }
217 ```
218
219 When the ***binder*** receives a request for the verb **hello** of
220 of the api **tuto-1**, it invoke the callback **hello** of the **binding**
221 with the argument **req** that handles the client request.
222
223 The callback has to treat synchronously or asynchronously the request and
224 should at the end emit a reply for the request.
225
226 At the line 7, the callback for **tuto-1/hello** replies to the request **req**.
227 Parameters of the reply are:
228
229  1. The first parameter is the replied request
230  2. The second parameter is a json object (here NULL)
231  3. The third parameter is the error string indication (here NULL: no error)
232  4. The fourth parameter is an informative string (that can be NULL) that can be used to provide meta data.
233
234 The 3 last parameters are sent back to the client as the reply content.
235
236 <!-- pagebreak -->
237
238 ## Sample binding: tuto-2
239
240 The second tutorial shows many important feature that can
241 commonly be used when writing a ***binding***:
242
243 - initialization, getting arguments, sending replies, pushing events.
244
245 This is the code of the binding **tuto-2.c**:
246
247 ```C
248       1 #include <string.h>
249       2 #include <json-c/json.h>
250       3 
251       4 #define AFB_BINDING_VERSION 3
252       5 #include <afb/afb-binding.h>
253       6 
254       7 afb_event_t event_login, event_logout;
255       8 
256       9 void login(afb_req_t req)
257      10 {
258      11         json_object *args, *user, *passwd;
259      12         char *usr;
260      13 
261      14         args = afb_req_json(req);
262      15         if (!json_object_object_get_ex(args, "user", &user)
263      16          || !json_object_object_get_ex(args, "password", &passwd)) {
264      17                 AFB_REQ_ERROR(req, "login, bad request: %s", json_object_get_string(args));
265      18                 afb_req_reply(req, NULL, "bad-request", NULL);
266      19         } else if (afb_req_context_get(req)) {
267      20                 AFB_REQ_ERROR(req, "login, bad state, logout first");
268      21                 afb_req_reply(req, NULL, "bad-state", NULL);
269      22         } else if (strcmp(json_object_get_string(passwd), "please")) {
270      23                 AFB_REQ_ERROR(req, "login, unauthorized: %s", json_object_get_string(args));
271      24                 afb_req_reply(req, NULL, "unauthorized", NULL);
272      25         } else {
273      26                 usr = strdup(json_object_get_string(user));
274      27                 AFB_REQ_NOTICE(req, "login user: %s", usr);
275      28                 afb_req_session_set_LOA(req, 1);
276      29                 afb_req_context_set(req, usr, free);
277      30                 afb_req_reply(req, NULL, NULL, NULL);
278      31                 afb_event_push(event_login, json_object_new_string(usr));
279      32         }
280      33 }
281      34 
282      35 void action(afb_req_t req)
283      36 {
284      37         json_object *args, *val;
285      38         char *usr;
286      39 
287      40         args = afb_req_json(req);
288      41         usr = afb_req_context_get(req);
289      42         AFB_REQ_NOTICE(req, "action for user %s: %s", usr, json_object_get_string(args));
290      43         if (json_object_object_get_ex(args, "subscribe", &val)) {
291      44                 if (json_object_get_boolean(val)) {
292      45                         AFB_REQ_NOTICE(req, "user %s subscribes to events", usr);
293      46                         afb_req_subscribe(req, event_login);
294      47                         afb_req_subscribe(req, event_logout);
295      48                 } else {
296      49                         AFB_REQ_NOTICE(req, "user %s unsubscribes to events", usr);
297      50                         afb_req_unsubscribe(req, event_login);
298      51                         afb_req_unsubscribe(req, event_logout);
299      52                 }
300      53         }
301      54         afb_req_reply(req, json_object_get(args), NULL, NULL);
302      55 }
303      56 
304      57 void logout(afb_req_t req)
305      58 {
306      59         char *usr;
307      60 
308      61         usr = afb_req_context_get(req);
309      62         AFB_REQ_NOTICE(req, "login user %s out", usr);
310      63         afb_event_push(event_logout, json_object_new_string(usr));
311      64         afb_req_session_set_LOA(req, 0);
312      65         afb_req_context_clear(req);
313      66         afb_req_reply(req, NULL, NULL, NULL);
314      67 }
315      68 
316      69 int preinit(afb_api_t api)
317      70 {
318      71         AFB_API_NOTICE(api, "preinit");
319      72         return 0;
320      73 }
321      74 
322      75 int init(afb_api_t api)
323      76 {
324      77         AFB_API_NOTICE(api, "init");
325      78         event_login = afb_api_make_event(api, "login");
326      79         event_logout = afb_api_make_event(api, "logout");
327      80         if (afb_event_is_valid(event_login) && afb_event_is_valid(event_logout))
328      81                 return 0;
329      82         AFB_API_ERROR(api, "Can't create events");
330      83         return -1;
331      84 }
332      85 
333      86 const afb_verb_t verbs[] = {
334      87         { .verb="login", .callback=login },
335      88         { .verb="action", .callback=action, .session=AFB_SESSION_LOA_1 },
336      89         { .verb="logout", .callback=logout, .session=AFB_SESSION_LOA_1 },
337      90         { .verb=NULL }
338      91 };
339      92 
340      93 const afb_binding_t afbBindingExport = {
341      94         .api = "tuto-2",
342      95         .specification = NULL,
343      96         .verbs = verbs,
344      97         .preinit = preinit,
345      98         .init = init,
346      99         .noconcurrency = 0
347     100 };
348 ```
349
350 Compiling:
351
352 ```bash
353 gcc -fPIC -shared tuto-2.c -o tuto-2.so $(pkg-config --cflags --libs afb-daemon)
354 ```
355
356 Running:
357
358 ```bash
359 afb-daemon --binding tuto-2.so --port 3333 --token ''
360 ```
361
362 Testing:
363
364 ```bash
365 $ afb-client-demo -H localhost:3333/api?token=toto
366 tuto-2 login {"help":true}
367 ON-REPLY 1:tuto-2/login: ERROR
368 {
369   "jtype":"afb-reply",
370   "request":{
371     "status":"bad-request",
372     "uuid":"e2b24a13-fc43-487e-a5f4-9266dd1e60a9"
373   }
374 }
375 tuto-2 login {"user":"jose","password":"please"}
376 ON-REPLY 2:tuto-2/login: OK
377 {
378   "jtype":"afb-reply",
379   "request":{
380     "status":"success"
381   }
382 }
383 tuto-2 login {"user":"jobol","password":"please"}
384 ON-REPLY 3:tuto-2/login: ERROR
385 {
386   "jtype":"afb-reply",
387   "request":{
388     "status":"bad-state"
389   }
390 }
391 tuto-2 action {"subscribe":true}
392 ON-REPLY 4:tuto-2/action: OK
393 {
394   "response":{
395     "subscribe":true
396   },
397   "jtype":"afb-reply",
398   "request":{
399     "status":"success"
400   }
401 }
402 ```
403
404 In an other terminal:
405
406 ```bash
407 $ afb-client-demo -H localhost:3333/api?token=toto
408 tuto-2 login {"user":"jobol","password":"please"}
409 ON-REPLY 1:tuto-2/login: OK
410 {
411   "jtype":"afb-reply",
412   "request":{
413     "status":"success",
414     "uuid":"a09f55ff-0e89-4f4e-8415-c6e0e7f439be"
415   }
416 }
417 tuto-2 logout true
418 ON-REPLY 2:tuto-2/logout: OK
419 {
420   "jtype":"afb-reply",
421   "request":{
422     "status":"success"
423   }
424 }
425 ```
426
427 It produced in the first terminal:
428
429 ```bash
430 ON-EVENT tuto-2/login:
431 {
432   "event":"tuto-2\/login",
433   "data":"jobol",
434   "jtype":"afb-event"
435 }
436 ON-EVENT tuto-2/logout:
437 {
438   "event":"tuto-2\/logout",
439   "data":"jobol",
440   "jtype":"afb-event"
441 }
442 ```