{"id":1765,"date":"2015-04-05T15:58:35","date_gmt":"2015-04-05T15:58:35","guid":{"rendered":"http:\/\/anthroponaute.fr\/blog-informatique\/?p=1765"},"modified":"2016-07-18T11:20:29","modified_gmt":"2016-07-18T11:20:29","slug":"introduction-a-la-programmation-reseau","status":"publish","type":"post","link":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/?p=1765","title":{"rendered":"Introduction \u00e0 la programmation r\u00e9seau (Sockets) &#8211; partie 1"},"content":{"rendered":"<p><a href=\"https:\/\/anthropoya.cluster014.ovh.net\/blog-informatique\/wp-content\/uploads\/2015\/04\/484052892.jpg\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-2251\" src=\"https:\/\/anthropoya.cluster014.ovh.net\/blog-informatique\/wp-content\/uploads\/2015\/04\/484052892.jpg\" alt=\"48405289\" width=\"318\" height=\"239\" srcset=\"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/wp-content\/uploads\/2015\/04\/484052892.jpg 318w, https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/wp-content\/uploads\/2015\/04\/484052892-300x225.jpg 300w\" sizes=\"(max-width: 318px) 100vw, 318px\" \/><\/a><\/p>\n<p><strong>Intro :<\/strong><\/p>\n<p>Pour concevoir un jeu vid\u00e9o multijoueur il vous faut utiliser dans le programme C++ des objets qu&rsquo;on appelle <strong>sockets<\/strong>. Ceci afin de pouvoir faire communiquer des ordinateurs \u00e0 distance.<\/p>\n<p><strong>Pr\u00e9requis :<\/strong><\/p>\n<p>&#8211; savoir un peu lire et \u00e9crire du C++<\/p>\n<p>&#8211; \u00eatre sous Windows.<\/p>\n<p><strong>Explications :<\/strong><\/p>\n<p>Les sockets sont des objets repr\u00e9sentants une interface de connexion <strong>li\u00e9e \u00e0 une machine<\/strong> afin de faire communiquer les ordinateurs \u00e0 travers un r\u00e9seau (<em>a fortiori<\/em> par Internet)<\/p>\n<p><strong>1 &#8211;<\/strong> Tout d&rsquo;abord je vais vous pr\u00e9senter un petit programme qui affiche une page HTML dans la console \u00e0 partir d&rsquo;une URL web en utilisant les sockets. Rien ne vaut un bel exemple concret pour comprendre !<\/p>\n<p><strong>2 &#8211;<\/strong> Aussi, comme il est important de conna\u00eetre de quoi l&rsquo;on parle, je vais \u00e9num\u00e9rer toutes les fonctions et mots-cl\u00e9s utilis\u00e9s dans la programmation socket en C.<\/p>\n<p>&nbsp;<\/p>\n<hr \/>\n<p>&nbsp;<\/p>\n<p><strong>1 &#8211;<\/strong> Voici ce premier programme :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/** Inspirez-vous des commentaires pour comprendre **\/\r\n\r\n#include &lt;winsock2.h&gt;\r\n#include &lt;stdio.h&gt;\r\n#include &lt;stdlib.h&gt;\r\n\r\n\/\/ Fonction qui permet d'afficher le contenu d'une page web\r\nvoid FetchAndPrintPage(SOCKET socket, const char* sHost, const char* sPage)\r\n{\r\n    \/\/ Cha\u00eene de caract\u00e8re repr\u00e9sentant la requ\u00eate HTTP\r\n\u00a0\u00a0\u00a0 const char* sRequestFormat = &quot;GET %s HTTP\/1.0\\r\\nHost: %s\\r\\nUser-Agent: FetchAndPrint.c\\r\\n\\r\\n&quot;;\r\n\u00a0\u00a0 \u00a0char* sMsg = new char[4096];\r\n\r\n    \/\/ On compose les param\u00e8tres dans une cha\u00eene de caract\u00e8res finale\r\n    \/\/ repr\u00e9sentant la requ\u00eate HTTP \u00e0 envoyer\r\n\u00a0\u00a0 \u00a0sprintf(sMsg, sRequestFormat, sPage, sHost);\r\n\r\n    \/\/ La fonction send() permet d'envoyer des informations vers\r\n    \/\/ l'ordinateur distant (le serveur) ; ici on envoit la requ\u00eate HTTP\r\n\u00a0\u00a0 \u00a0if (send(socket, sMsg, strlen(sMsg), 0) != SOCKET_ERROR)\r\n\u00a0\u00a0 \u00a0{\r\n        \/\/ Comme la fonction recv(), qui suit, ne r\u00e9ceptionne qu'une partie du message\r\n        \/\/ re\u00e7u, il faut l'invoquer en boucle jusqu'\u00e0 ce que le message\r\n        \/\/ est enti\u00e8rement r\u00e9ceptionn\u00e9.\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0while (true)\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0{\r\n            \/\/ Le nombre d'octet re\u00e7u\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0int iBytes;\r\n            \/\/ Le tampon de caract\u00e8res stockant le contenu de la page web\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0char sBuffer[4096];\r\n\r\n            \/\/ La fonction recv() ne r\u00e9ceptionne qu'une partie du message\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0     \/\/ re\u00e7u ; elle retourne le nombre d'octets re\u00e7u et stocke ce contenu\r\n            \/\/ dans la variable tampon sBuffer\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0iBytes = recv(socket, sBuffer, 4096, 0);\r\n\r\n            \/\/ On sort de la boucle s'il n'y a plus de message \u00e0 receptionner ou\r\n            \/\/ qu'une erreur a eu lieu !\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0if (iBytes == 0 &amp;&amp; iBytes == -1)\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0{\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0break;\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\r\n            \/\/ Pour finaliser la cha\u00eene on doit rajouter le caract\u00e8re nul \u00e0 celle-ci\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0sBuffer[iBytes-1] = '&#92;&#48;';\r\n\r\n            \/\/ On affiche le contenu de la page web dans la console\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0printf(&quot;\\n\\n%s\\n&quot;, sBuffer);\r\n            \/\/ On met en pause le programme afin de lire la portion du texte re\u00e7u\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0system(&quot;pause&quot;);\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0else\r\n\u00a0\u00a0 \u00a0{\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0printf(&quot;Socket Error Code = %i\\n&quot;, WSAGetLastError());\r\n\u00a0\u00a0 \u00a0}\r\n\r\n\u00a0\u00a0 \u00a0delete sMsg;\r\n}\r\n\r\nint main()\r\n{\r\n    \/\/ Ces deux suivantes instructions initialisent\r\n    \/\/ l'utilisation des sockets sous Windows\r\n\u00a0\u00a0 \u00a0WSADATA WSAData;\r\n\u00a0\u00a0 \u00a0WSAStartup(MAKEWORD(2,2), &amp;WSAData);\r\n\r\n    \/\/ Structure utilis\u00e9e afin de stocker les informations d'un socket\r\n\u00a0\u00a0 \u00a0in_addr addr;\r\n\r\n\u00a0\u00a0 \u00a0const char* sHost = &quot;anthroponaute.fr&quot;;\r\n\r\n    \/\/ Cette fonction cr\u00e9\u00e9 un socket\r\n\u00a0\u00a0 \u00a0SOCKET s = socket(AF_INET, SOCK_STREAM, 0);\r\n\r\n\u00a0\u00a0 \u00a0if (s == INVALID_SOCKET)\r\n\u00a0\u00a0 \u00a0{\r\n        \/\/ Affiche la signification de l'erreur\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0printf(&quot;Socket Error Code = %i\\n&quot;, WSAGetLastError());\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0system(&quot;pause&quot;);\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit(EXIT_FAILURE);\r\n\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0\r\n    \/\/ Pour obtenir des informations socket sur cette adresse web\r\n\u00a0\u00a0 \u00a0hostent* pHost = gethostbyname(sHost);\r\n\r\n\u00a0\u00a0 \u00a0if (pHost != nullptr)\r\n\u00a0\u00a0 \u00a0{\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0printf(&quot;Adress Name = %s\\n&quot;, pHost-&gt;h_name);\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 addr.s_addr = *(u_long*) pHost-&gt;h_addr_list[0];\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 printf(&quot;IP Address = %s\\n&quot;, inet_ntoa(addr));\r\n\u00a0\u00a0 \u00a0}\r\n\u00a0\u00a0 \u00a0else\r\n\u00a0\u00a0 \u00a0{\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0printf(&quot;Socket Error Code = %i&quot;, WSAGetLastError());\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 system(&quot;pause&quot;);\r\n\r\n        exit(EXIT_FAILURE);\r\n\u00a0\u00a0 \u00a0}\r\n\r\n    \/\/ On remplit cette structure afin de configurer et situer notre socket\r\n\u00a0\u00a0 \u00a0SOCKADDR_IN adress_in;\r\n\u00a0\u00a0 \u00a0adress_in.sin_family = AF_INET;\r\n\u00a0\u00a0 \u00a0adress_in.sin_addr = addr;\r\n\u00a0\u00a0 \u00a0adress_in.sin_port = htons(80);\r\n\r\n\u00a0\u00a0 \u00a0\/*************\/\r\n\r\n    \/\/ On se connecte sur le serveur web\r\n\u00a0\u00a0\u00a0 if (connect(s, (SOCKADDR*)&amp;adress_in, sizeof(adress_in)) == SOCKET_ERROR)\r\n\u00a0\u00a0 \u00a0{\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0printf(&quot;Socket Error Code = %i\\n&quot;, WSAGetLastError());\r\n\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0system(&quot;pause&quot;);\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 exit(EXIT_FAILURE);\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0 \u00a0else\r\n\u00a0\u00a0 \u00a0{\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0 \u00a0printf(&quot;Connected !\\n&quot;);\r\n\u00a0\u00a0 \u00a0}\r\n    \/\/ On affiche la page du site en sp\u00e9cifiant son r\u00e9pertoire\r\n\u00a0\u00a0 \u00a0FetchAndPrintPage(s, sHost, &quot;\/blog-informatique\/&quot;);\r\n\r\n\u00a0\u00a0 \u00a0WSACleanup();\r\n\r\n\u00a0\u00a0 \u00a0return 0;\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Copiez \/ collez et compilez !<\/p>\n<p>&nbsp;<\/p>\n<hr \/>\n<p>&nbsp;<\/p>\n<p><strong>2 &#8211;<\/strong> Voici maintenant tous les <strong>principes<\/strong>, <strong>concepts<\/strong> et <strong>fonctions\u00a0<\/strong>de la programmation de sockets :<\/p>\n<p>Voici les fichiers<strong> d&rsquo;en-t\u00eate<\/strong> n\u00e9cessaires :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#include &lt;winsock2.h&gt;\r\n#include &lt;stdio.h&gt;\r\n#include &lt;stdlib.h&gt;\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Voici les <strong>#define<\/strong> pr\u00e9\u00e9tablis :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nSOCKET pour l'utilisation de socket (\u00e0 la place de int)\r\nSOCKADDR_IN pour struct sockaddr_in\r\nSOCKADDR pour struct sockaddr\r\nIN_ADDR pour struct in_addr\r\nINVALID_SOCKET -1\r\nSOCKET_ERROR -1\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Voici la <strong>structure principale<\/strong> \u00e0 utiliser :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct sockaddr_in\r\n{\r\n\u00a0\u00a0\u00a0 short\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sin_family;\r\n\u00a0\u00a0\u00a0 unsigned short\u00a0\u00a0\u00a0\u00a0 sin_port;\r\n\u00a0\u00a0\u00a0 struct\u00a0\u00a0 in_addr\u00a0\u00a0 sin_addr;\r\n\u00a0\u00a0\u00a0 char\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 sin_zero[8];\r\n};\r\n<\/pre>\n<p>Elle est utilis\u00e9e pour configurer la socket.<\/p>\n<p>&nbsp;<\/p>\n<p>Voici comment l&rsquo;utiliser :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nSOCKADDR_IN sin;\r\nsin.sin_addr.s_addr = htonl(INADDR_ANY);\u00a0 \u00a0\r\nsin.sin_family = AF_INET;\r\nsin.sin_port = htons(23);\r\n<\/pre>\n<p>&#8211; <strong>sin.sin_addr<\/strong>.s_addr : c&rsquo;est l&rsquo;adresse qu&rsquo;on affecte \u00e0 cette configuration de socket (c&rsquo;est peut-\u00eatre l&rsquo;adresse repr\u00e9sentant un <strong>client<\/strong> ou un <strong>serveur<\/strong>, cela d\u00e9pend du r\u00f4le de notre socket)<\/p>\n<p>&#8211; <strong>INADDR_ANY<\/strong> : adresse IP automatique<\/p>\n<p>Pour sp\u00e9cifier une adresse manuellement on peut utiliser la fonction <strong>inet_addr()<\/strong><\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nsin.sin_addr.s_addr = inet_addr(&quot;127.0.0.1&quot;);\r\n<\/pre>\n<p>&#8211; <strong>sin.sin_family = AF_INET<\/strong> : sp\u00e9cifie que l&rsquo;on passe la socket en mode Internet<\/p>\n<p>&#8211; <strong>sin.sin_port<\/strong> : sp\u00e9cifie le num\u00e9ro de port de la socket \u00e0 utiliser<\/p>\n<p><span style=\"text-decoration: underline;\">En occurrence, qu&rsquo;est-ce qu&rsquo;un port ?<\/span><\/p>\n<p>Un port sert \u00e0 sp\u00e9cifier \u00e0 quel service d&rsquo;application les informations doivent \u00eatre envoy\u00e9es.<\/p>\n<p>En effet sur le r\u00e9seau Internet global, chaque ordinateur est repr\u00e9sent\u00e9 par un num\u00e9ro d&rsquo;IP, mais cette adresse IP ne sp\u00e9cifie pas \u00e0 quelle application de l&rsquo;ordinateur l&rsquo;information doit \u00eatre envoy\u00e9.<\/p>\n<p>En cons\u00e9quence les applications \/ services sont identifi\u00e9s par des num\u00e9ros de port (ex : port 80 pour<br \/>\nle service HTTP)<\/p>\n<p>&nbsp;<\/p>\n<p>Ces deux fonctions doivent \u00eatre appel\u00e9es pour initialiser ou cl\u00f4turer l&rsquo;utilisation des sockets :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ Au d\u00e9but du programme :\r\nWSADATA WSAData;\r\nWSAStartup(MAKEWORD(2,2), &amp;WSAData);\r\n\r\n\/\/ A la fin du programme :\r\nWSACleanup();\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Pour initialiser une socket :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nSOCKET sock = socket(AF_INET, SOCK_STREAM, 0);\r\n<\/pre>\n<p>Voici les diff\u00e9rents param\u00e8tres de cette fonction :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nAF_INET \/\/ Sp\u00e9ficie que l'on va utiliser le mode Internet\r\n<\/pre>\n<p>Types de socket :<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nSOCK_STREAM\u00a0 \/\/ Sp\u00e9ficie que l'on va utiliser le protocole TCP\/IP. \r\n\r\nSOCK_DGRAM \/\/ Sp\u00e9ficie que l'on va utiliser le protocole UDP\/IP.\r\n<\/pre>\n<p>&#8211; Le protocole TCP est un protocole dit connect\u00e9. Il contr\u00f4le si le paquet<br \/>\nest arriv\u00e9 \u00e0 destination si ce n&rsquo;est pas le cas il le renvoie.<\/p>\n<p>&#8211; Le procotole UDP est un protocole dit non connect\u00e9. A l&rsquo;inverse du protocole TCP<br \/>\nil ne contr\u00f4le pas si le paquet est arriv\u00e9 \u00e0 destination.<\/p>\n<p>&nbsp;<\/p>\n<p>Cette fonction permet de convertir une addresse IP (en char*) afin d&rsquo;\u00eatre<br \/>\naffect\u00e9e \u00e0 la structure IN_ADDR.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\ninet_addr(&quot;127.0.0.1&quot;);\r\n<\/pre>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\ninet_ntoa()\r\n<\/pre>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint connect(int socket, struct sockaddr* addr, socklen_t addrlen);\r\n<\/pre>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint bind(int socket, const struct sockaddr* addr, socklen_t addrlen);\r\n<\/pre>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint listen(int socket, int backlog);\r\n<\/pre>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint accept(int socket, struct sockaddr* addr, socklen_t* addrlen);\r\n<\/pre>\n<p><strong>R\u00e9sum\u00e9 :<\/strong><\/p>\n<p>Nous avons cr\u00e9\u00e9 un programme qui affiche une page web afin de comprendre la programmation sockets.<\/p>\n<p>Au final nous avons \u00e9num\u00e9r\u00e9 tous les concepts li\u00e9s \u00e0 la programmation sockets.<\/p>\n<p><strong>R\u00e9f\u00e9rences :<\/strong><\/p>\n<p>&#8211; http:\/\/pub.phyks.me\/sdz\/sdz\/les-sockets.html<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Intro : Pour concevoir un jeu vid\u00e9o multijoueur il vous faut utiliser dans le programme C++ des objets qu&rsquo;on appelle sockets. Ceci afin de pouvoir faire communiquer des ordinateurs \u00e0 distance. Pr\u00e9requis : &#8211; savoir un peu lire et \u00e9crire du C++ &#8211; \u00eatre sous Windows. Explications : Les sockets sont des objets repr\u00e9sentants une [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[22],"tags":[],"_links":{"self":[{"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=\/wp\/v2\/posts\/1765"}],"collection":[{"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1765"}],"version-history":[{"count":81,"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=\/wp\/v2\/posts\/1765\/revisions"}],"predecessor-version":[{"id":2553,"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=\/wp\/v2\/posts\/1765\/revisions\/2553"}],"wp:attachment":[{"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1765"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1765"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.la-porte-des-nebuleuses.net\/blog-informatique\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1765"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}