Tutorial, Doc: Added second chapter for network tutorial. It is about building RESTful server.

This commit is contained in:
Zbigniew Rębacz 2023-10-23 01:21:21 +02:00
parent ec34605fee
commit 9b877c653d
3 changed files with 227 additions and 8 deletions

View file

@ -0,0 +1,57 @@
#include <Core/Core.h>
using namespace Upp;
constexpr int port = 4000;
void ProcessHttpRequest(TcpSocket& client)
{
HttpHeader header;
if(!header.Read(client)) {
Cerr() << "Failed to read HttpHeader.\n";
HttpResponse(client, false, 400, "Invalid request");
return;
}
auto path = header.GetURI();
if(header.GetMethod() == "GET") {
if(path == "/countries") {
JsonArray ja;
ja << "Czech Republic"
<< "Indonesia"
<< "Brazil"
<< "France";
HttpResponse(client, false, 200, "OK", "application/json", ja.ToString());
}
}
HttpResponse(client, false, 404, "Not found");
}
void RunServerLoop(TcpSocket& server)
{
for(;;) {
TcpSocket client;
Cout() << "Waiting for incoming connection from the client...\n";
if(!client.Accept(server)) {
Cerr() << "Connection from the client not accepted.\n";
continue;
}
ProcessHttpRequest(client);
}
}
CONSOLE_APP_MAIN
{
TcpSocket server;
if(!server.Listen(port)) {
Cerr() << "Cannot open server port for listening with error \"" << server.GetErrorDesc()
<< "\".\n";
return;
}
RunServerLoop(server);
}

View file

@ -0,0 +1,11 @@
description "Creating simple REST service\377";
uses
Core;
file
Network02.cpp;
mainconfig
"" = "";

View file

@ -37,17 +37,17 @@ to launch them or experiment with the code. Good luck!&]
is not yet available.&]
[s3;%% Table of contents&]
[s0;%% &]
[s0;%% [^topic`:`/`/Core`/srcdoc`/NetworkTutorial`_en`-us`#1^ 1. HTTP
Request]&]
[s0;%% [^topic`:`/`/Core`/srcdoc`/NetworkTutorial`_en`-us`#1`_1^ 1.1
Obtaining data from REST API]&]
[s0;%% [^topic`:`/`/Core`/srcdoc`/NetworkTutorial`_en`-us`#1^ 1. Obtaining
data from REST API via HTTP Request]&]
[s0; [^topic`:`/`/Core`/srcdoc`/NetworkTutorial`_en`-us`#2^ 2 Building
RESTful server]&]
[s0;^topic`:`/`/CtrlLib`/srcdoc`/Tutorial`_en`-us`#1`_2^%% &]
[s3;:1:%% 1. HTTP Request&]
[s22;:1`_1:%% 1.1 Obtaining data from REST API&]
[s3;:1:%% 1. Obtaining data from REST API via HTTP Request&]
[s5;%% One of the most popular APIs available now on the internet
is the REST API. In this tutorial, you will learn how to use
the [* HTTPRequest] class to communicate with such an API and use
the obtained data directly in the application.&]
the [*^topic`:`/`/Core`/src`/HttpRequest`_en`-us`#HttpRequest`:`:class^ HTTPRequest
] class to communicate with such an API and use the obtained
data directly in the application.&]
[s5;%% [* HTTPRequest] is a class that performs synchronous or asynchronous
HTTP and [* HTTPS] requests. HTTPS requests are very common today.
More than 80% of services use this kind of connection. To support
@ -155,4 +155,155 @@ pRequest][*_C@(0.0.255) `::][*_C METHOD`_GET)][C@(0.0.255) .][C Execute();]&]
[s5; Is it exactly the same as:&]
[s0;l320; [*C@(0.0.255) auto][C content ][C@(0.0.255) `=][C http][C@(0.0.255) .][*_C GET()][C@(0.0.255) .
][C Execute();]&]
[s5; &]
[s3;:2: 2 Building RESTful server&]
[s5; Now, it`'s time to build our own RESTful server, and don`'t
look at others. In this tutorial, we will build a simple service
with one node [* /countries] that will return predefined countries.
We need to start by creating a server that will listen on any
port that is not yet taken. To do it, let`'s create a [*^topic`:`/`/Core`/src`/TcpSocket`_en`-us`#TcpSocket`:`:class^ T
cpSocket] instance and then call the [*^topic`:`/`/Core`/src`/TcpSocket`_en`-us`#TcpSocket`:`:Listen`(int`,int`,bool`,bool`,void`*`)^ L
isten] method. This method, in its simplest form, requires providing
a port as an argument. The safe value in this case for development
purposes is 4000. Please keep in mind that the default port for
[* HTTP is 80] and for [* HTTPS is 443]. In the production enviromenet,
these ports must be used. However, in some operating systems,
these ports may be blocked by default, and to unlock them, some
additional actions might be needed. So, to save ourselves the
trouble, we will use a different port.&]
[s5; After that, we will create a loop for our server logic. In each
loop iteration, we will handle one request from the client. To
do that, there is a need for the creation of a separate [* TcpSocket]
instance dedicated to handling that request. To accept an incoming
connection from the client, there is a need to call the socket
[*^topic`:`/`/Core`/src`/TcpSocket`_en`-us`#TcpSocket`:`:class^ Accept][*
]method. The arugmenet to this method is socket. In our case,
we need to pass a server socket. The code for this section is
implemented in the RunServerLoop() function in the code sample
attached to this paragraph.&]
[s5; In the very final step, we will handle the client request. To
do that, we need to read the HTTP header provided by the client.
The header contains many valuable pieces of information, such
as the URI that was passed by the client and the HTTP method
that was used. To obtain the header, let`'s create an [*^topic`:`/`/Core`/src`/HttpHeader`_en`-us`#HttpHeader`:`:struct^ H
ttpHeader] class instance and then call the [*^topic`:`/`/Core`/src`/HttpHeader`_en`-us`#HttpHeader`:`:Read`(TcpSocket`&`)^ R
ead] method, with the socket responsible for handling connections
to the client as a parameter. In order to handle client requests,
we will need to use the following methods of the HttpHeader class:&]
[s5;l320;i150;O0; [* GetURI()] `- returns URI associated with the header.
We will use this information to process client request. Moreover,
URI can also contain additional parameteres to the query.&]
[s5;l320;i150;O0; [* GetMethod()] `- returns HTTP method associated
with the header.&]
[s5; In the end, to send the response to the client, the [*^topic`:`/`/Core`/src`/Inet`_en`-us`#HttpResponse`(TcpSocket`&`,bool`,int`,const char`*`,const char`*`,const String`&`,const char`*`,bool`)^ H
ttpResponse] function must be used. To use it properly, you need
to provide a client socket, a status code, and optionally, if
you don`'t return error content type and data. After providing
parameters, the function will return all specified information
directly back to the client.&]
[s5; main.cpp&]
[s0;l320; [C@(128.0.255) #include][C ][C@(0.0.255) <][C Core][C@(0.0.255) /][C Core][C@(0.0.255) .
][C h][C@(0.0.255) >]&]
[s0;l320;C &]
[s0;l320; [*C@(0.0.255) using][C ][*C@(0.0.255) namespace][C Upp;]&]
[s0;l320;C &]
[s0;l320; [*C@(0.0.255) constexpr][C ][*C@(0.0.255) int][C SERVER`_PORT
][C@(0.0.255) `=][C ][C@3 4000][C ;]&]
[s0;l320;C &]
[s0;l320; [*C@(0.0.255) void][C ProcessHttpRequest(TcpSocket][C@(0.0.255) `&][C
client)]&]
[s0;l320; [C `{]&]
[s0;l320; [C ][*_C HttpHeader header;]&]
[s0;l320; [C ][*C@(0.0.255) if][C (][C@(0.0.255) !][*_C header][*_C@(0.0.255) .][*_C Read(clien
t)][C ) `{]&]
[s0;l320; [C Cerr() ][C@(0.0.255) <<][C ][C@3 `"Failed to read HttpHeader.][C@(0.0.255) `\
n][C@3 `"][C ;]&]
[s0;l320; [C HttpResponse(client, ][*C@(0.0.255) false][C , ][C@3 400][C ,
][C@3 `"Invalid request`"][C );]&]
[s0;l320; [C ][*_C@(128.0.255) return][C ;]&]
[s0;l320; [C `}]&]
[s0;l320;C &]
[s0;l320; [C ][*C@(0.0.255) auto][C path ][C@(0.0.255) `=][C ][*_C header][*_C@(0.0.255) .][*_C G
etURI()][C ;]&]
[s0;l320; [C ][*C@(0.0.255) if][C (][*_C header][*_C@(0.0.255) .][*_C GetMethod()][C
][C@(0.0.255) `=`=][C ][C@3 `"GET`"][C ) `{]&]
[s0;l320; [C ][*C@(0.0.255) if][C (path ][C@(0.0.255) `=`=][C ][C@3 `"/countries`"][C )
`{]&]
[s0;l320; [C JsonArray ja;]&]
[s0;l320; [C ja ][C@(0.0.255) <<][C ][C@3 `"Czech Republic`"]&]
[s0;l320; [C ][C@(0.0.255) <<][C ][C@3 `"Indonesia`"]&]
[s0;l320; [C ][C@(0.0.255) <<][C ][C@3 `"Brazil`"]&]
[s0;l320; [C ][C@(0.0.255) <<][C ][C@3 `"France`"][C ;]&]
[s0;l320;C &]
[s0;l320; [C ][*_C HttpResponse(client, ][*_C@(0.0.255) false][*_C ,
][*_C@3 200][*_C , ][*_C@3 `"OK`"][*_C , ][*_C@3 `"application/json`"][*_C ,
ja][*_C@(0.0.255) .][*_C ToString())][C ;]&]
[s0;l320; [C `}]&]
[s0;l320; [C `}]&]
[s0;l320;C &]
[s0;l320; [C HttpResponse(client, ][*C@(0.0.255) false][C , ][C@3 404][C ,
][C@3 `"Not found`"][C );]&]
[s0;l320; [C `}]&]
[s0;l320;C &]
[s0;l320; [*C@(0.0.255) void][C RunServerLoop(TcpSocket][C@(0.0.255) `&][C
server)]&]
[s0;l320; [C `{]&]
[s0;l320; [C ][*C@(0.0.255) for][C (;;) `{]&]
[s0;l320; [C TcpSocket client;]&]
[s0;l320;C &]
[s0;l320; [C Cout() ][C@(0.0.255) <<][C ][C@3 `"Waiting for incoming
connection from the client...][C@(0.0.255) `\n][C@3 `"][C ;]&]
[s0;l320; [C ][*C@(0.0.255) if][C (][C@(0.0.255) !][*_C client][*_C@(0.0.255) .][*_C Accept
(server)][C ) `{]&]
[s0;l320; [C Cerr() ][C@(0.0.255) <<][C ][C@3 `"Connection from
the client not accepted.][C@(0.0.255) `\n][C@3 `"][C ;]&]
[s0;l320; [C ][*_C@(128.0.255) continue][C ;]&]
[s0;l320; [C `}]&]
[s0;l320;C &]
[s0;l320; [C ProcessHttpRequest(client);]&]
[s0;l320; [C `}]&]
[s0;l320; [C `}]&]
[s0;l320;C &]
[s0;l320; [C CONSOLE`_APP`_MAIN]&]
[s0;l320; [C `{]&]
[s0;l320; [C ][*_C TcpSocket server;]&]
[s0;l320; [C ][*C@(0.0.255) if][C (][C@(0.0.255) !][*_C server][*_C@(0.0.255) .][*_C Listen(SER
VER`_PORT)][C ) `{]&]
[s0;l320; [C Cerr() ][C@(0.0.255) <<][C ][C@3 `"Cannot open server
port for listening with error ][C@(0.0.255) `\`"][C@3 `"][C ][C@(0.0.255) <<][C
server][C@(0.0.255) .][C GetErrorDesc()]&]
[s0;l320; [C ][C@(0.0.255) <<][C ][C@3 `"][C@(0.0.255) `\`"][C@3 .][C@(0.0.255) `\n
][C@3 `"][C ;]&]
[s0;l320; [C ][*_C@(128.0.255) return][C ;]&]
[s0;l320; [C `}]&]
[s0;l320;C &]
[s0;l320; [C RunServerLoop(server);]&]
[s0;l320; [C `}]&]
[s5; To test above the example a [*^https`:`/`/curl`.se`/^ curl] terminal
application can be used. This application is bundled with most
Linux distributions, and it can be easily downloaded for Windows.
The command that should be run in the terminal is as follows:&]
[s0;l320;~~~32; [C$2 curl `-v http://127.0.0.1:4000/countries]&]
[s5; The output of the command should be:&]
[s0;l320; [C$2 `* __Trying 127.0.0.1:4000...]&]
[s0;l320; [C `* Connected to 127.0.0.1 (127.0.0.1) port 4000]&]
[s0;l320; [C > GET /countries HTTP/1.1]&]
[s0;l320; [C > Host: 127.0.0.1:4000]&]
[s0;l320; [C > User`-Agent: curl/8.4.0]&]
[s0;l320; [C > Accept: `*/`*]&]
[s0;l320; [C > _]&]
[s0;l320; [C < HTTP/1.1 200 OK]&]
[s0;l320; [C < Date: Sun, 22 Oct 2023 17:35:13 `+0200]&]
[s0;l320; [C < Server: U`+`+ based server]&]
[s0;l320; [C < Connection: close]&]
[s0;l320; [C < Content`-Length: 48]&]
[s0;l320; [C < Content`-Type: application/json]&]
[s0;l320; [C < _]&]
[s0;l320; [C `* Closing connection]&]
[s0;l320; [C `[`"Czech Republic`",`"Indonesia`",`"Brazil`",`"France`"`]]&]
[s5; As you can see in the above output, we received our desirable
response. The [* `-v] parameter stands for verbose mode, and it
is supposed to show additional information. It is very helpful
for debugging purposes. Without this parameter, the output is:&]
[s0;l320; [C$2 `[`"Czech Republic`",`"Indonesia`",`"Brazil`",`"France`"`]]&]
[s5; ]]