One place for hosting & domains

      How To Configure HAProxy Logging with Rsyslog on CentOS 8 [Quickstart]


      Introduction

      HAProxy, which stands for High Availability Proxy, is a widely used TCP and HTTP-based proxy server that runs on Linux, Solaris, and FreeBSD. It is used to load balance applications by distributing requests between multiple servers, and to ensure that applications are highly available for users.

      By default on many CentOS 8 installations, HAProxy is not configured to write its log output to a file. This quickstart tutorial will explain how to configure HAProxy logging with Rsyslog by using a Unix domain socket for reliability, speed, and security.

      Prerequisites

      To complete this tutorial, you will need a server running CentOS 8. This server should have a non-root user with administrative privileges. To set this up, follow the Initial Server Setup guide for CentOS 8 tutorial.

      Step 1 — Installing and Enabling HAProxy

      To install HAProxy, run the following dnf command:

      When you are prompted Is this ok [y/N]: enter y and press RETURN to install the package.

      Once you have installed HAProxy, enable and start it using the systemctl command:

      • sudo systemctl enable haproxy.service

      You should receive the following output:

      Output

      Created symlink /etc/systemd/system/multi-user.target.wants/haproxy.service → /usr/lib/systemd/system/haproxy.service.

      With the HAProxy service now enabled, you can start it up to ensure that it runs with a default configuration on your server:

      • sudo systemctl start haproxy.service

      Next examine HAProxy’s status to make sure it is running:

      • sudo systemctl status haproxy.service

      You should receive output like the following. Note the highlighted active (running) portion of the output. If your server shows the same highlighted section then HAProxy is running correctly on your server and you can proceed with configuring logging.

      Output

      ● haproxy.service - HAProxy Load Balancer Loaded: loaded (/usr/lib/systemd/system/haproxy.service; enabled; vendor preset: disabled) Active: active (running) since Wed 2020-09-09 21:16:39 UTC; 4min 39s ago Process: 21340 ExecStartPre=/usr/sbin/haproxy -f $CONFIG -c -q (code=exited, status=0/SUCCESS) Main PID: 21341 (haproxy) Tasks: 2 (limit: 2881) Memory: 2.7M CGroup: /system.slice/haproxy.service ├─21341 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid . . .

      If your output is different, or the status shows something like Active: failed, then follow the troubleshooting steps in the HAproxy Common Errors Series Introduction tutorial to determine what is preventing HAProxy from starting correctly.

      Once you have confirmed that HAProxy is enabled and running, you can continue to the next step, which is configuring HAProxy’s logging directives.

      Step 2 — Configuring HAProxy Logging Directives

      To configure HAProxy’s logging directives, open /etc/haproxy/haproxy.cfg in vi or your preferred editor:

      • sudo vi /etc/haproxy/haproxy.cfg

      Press i to switch to INSERT mode, then find the line log 127.0.0.1 local2 and comment it out by adding a # character to the beginning of the line, as highlighted in the following example:

      /etc/haproxy/haproxy.cfg

      . . .
      # 2) configure local2 events to go to the /var/log/haproxy.log
      #   file. A line like the following can be added to
      #   /etc/sysconfig/syslog
      #
      #    local2.*                       /var/log/haproxy.log
      #
          #log         127.0.0.1 local2
      
          chroot      /var/lib/haproxy
          pidfile     /var/run/haproxy.pid
      . . .
      

      Now add a line directly after the commented out line with the following contents:

          log         /dev/log local0
      

      The entire section of /etc/haproxy/haproxy.cfg that you edited should contain the following lines:

      /etc/haproxy/haproxy.cfg

      . . .
      #    local2.*                       /var/log/haproxy.log
      #
           #log         127.0.0.1 local2
           log         /dev/log local0
      
           chroot      /var/lib/haproxy
           pidfile     /var/run/haproxy.pid
      . . .
      

      The chroot line is important, because it restricts the HAProxy process to accessing files in the /var/lib/haproxy directory only. The log /dev/log local0 line will create a file inside that directory that Rsyslog will use to collect log entries from.

      Once you are finished editing the log lines in /etc/haproxy/haproxy.cfg, save and close the file by pressing ESC, typing :wq, and pressing ENTER.

      The last step that you need to complete in this section is to create the /var/lib/haproxy/dev directory since it does not exist by default.

      Create the directory using the mkdir command and then restart HAProxy:

      • sudo mkdir /var/lib/haproxy/dev
      • sudo systemctl restart haproxy.service

      You have now configured HAProxy to send its logs to a Unix domain socket that resides in /var/lib/haproxy/dev/log. In the next step, you will configure Rsyslog to create and access the socket.

      Step 3 — Configuring Rsyslog to Collect HAProxy Logs

      Rsyslog’s default configuration on CentOS 8 does not handle HAProxy logs. To collect logs from the HAProxy service, open a new file /etc/rsyslog.d/99-haproxy.conf using vi or your preferred editor:

      • sudo vi /etc/rsyslog.d/99-haproxy.conf

      Press i to switch to INSERT mode, then paste the following lines into the file:

      /etc/rsyslog.d/99-haproxy.conf

      $AddUnixListenSocket /var/lib/haproxy/dev/log
      
      # Send HAProxy messages to a dedicated logfile
      :programname, startswith, "haproxy" {
        /var/log/haproxy.log
        stop
      }
      

      The $AddUnixListenSocket directive tells Rsyslog to create a Unix domain socket in the specified location, in this case /var/lib/haproxy/dev/log. The :programname, startswith, "haproxy" section specifies the file where Rsyslog will write the log entries to that it collects from the socket.

      Once you are finished editing /etc/rsyslog.d/99-haproxy.conf, save and close the file by pressing ESC, typing :wq, and pressing ENTER.

      You have now configured Rsyslog to read log entries from the Unix domain socket in /var/lib/haproxy/dev/log and write them to a log file in /var/log/haproxy.log.

      However, before restarting Rsyslog you will need to determine if SELinux is enforcing access control on your CentOS 8 system.

      To check SELinux’s current policy, run the following:

      You will receive one of the following outputs:

      • Enforcing – In this mode, SELinux is enforcing access controls on your system. You will need to complete the following optional Step 4 — Configuring SELinux section.
      • Permissive – In this case, SELinux logs all access attempts to its log file, but does not enforce access controls on your system.
      • Disabled – If SELinux is disabled, then it is not logging or enforcing any access control policies on your system.

      If the getenforce command returned either Permissive or Disabled, then you can restart Rsyslog with the following command:

      • sudo systemctl restart rsyslog

      Once you restart Rsyslog, you will be able to view logs in the /var/log/haproxy.log file that you configured in /etc/rsyslog.d/99-haproxy.conf. Proceed to Step 5 — Testing HAProxy Logging to make sure that everything is working as expected.

      Otherwise, if your system is running SELinux in Enforcing mode, then the next section of this tutorial explains how to add a module to allow Rsyslog and HAProxy to communicate with each other over their shared Unix domain socket.

      Step 4 — (Optional) Configuring SELinux

      If your CentOS 8 system is configured with SELinux in Enforcing mode, then you will need to allow Rsyslog access to HAProxy’s chroot directory. Allowing this access will let Rsyslog create the Unix domain socket that HAproxy will send its logs to.

      If you are not familiar with SELinux, this tutorial series An Introduction to SELinux on CentOS 7 will help you learn how to manage and interact with SELinux. Although it is written for CentOS 7, the principles and commands in the series are equally applicable to CentOS 8.

      To enable Rsyslog and HAProxy access to their shared socket, the first task is to create a Type Enforcement policy file. Open a new file called rsyslog-haproxy.te in vi or your preferred editor:

      Press i to switch to INSERT mode, then paste the following lines into the file:

      rsyslog-haproxy.te

      module rsyslog-haproxy 1.0;
      
      require {
          type syslogd_t;
          type haproxy_var_lib_t;
          class dir { add_name remove_name search write };
          class sock_file { create setattr unlink };
      }
      
      #============= syslogd_t ==============
      allow syslogd_t haproxy_var_lib_t:dir { add_name remove_name search write };
      allow syslogd_t haproxy_var_lib_t:sock_file { create setattr unlink };
      

      The first line defines the module name and version. The require portion tells the SELinux module loader about the types and classes that are required for the policy to be loaded as a module. The last two lines are the rules that allow Rsyslog access to HAProxy’s chroot and socket file respectively.

      When you are done editing the file, save and close it by pressing ESC, typing :wq, and pressing ENTER.

      Next, run the following command to install the checkpolicy package, which contains the checkmodule utility that you will use to turn the Type Enforcement file into an SELinux module.

      • sudo dnf install checkpolicy

      Now that you have the checkmodule tool installed, the next step is to compile the module and then load it into SELinux. Run the following to compile the Type Enforcement file into an SELinux module:

      • checkmodule -M -m rsyslog-haproxy.te -o rsyslog-haproxy.mod

      Next, run semodule_package to generate a complete policy package that SELinux can load into the Linux kernel:

      • semodule_package -o rsyslog-haproxy.pp -m rsyslog-haproxy.mod

      The final step is to load the package that you generated into the Linux kernel using the semodule command:

      • sudo semodule -i rsyslog-haproxy.pp

      Adding the module may take a few seconds. Once the command completes you can confirm that the module is loaded into the kernel by running the semodule command:

      • sudo semodule -l |grep rsyslog-haproxy

      You should receive output like the following:

      Output

      rsyslog-haproxy

      Once the module is loaded you can restart Rsyslog with the following command:

      • sudo systemctl restart rsyslog

      You have now defined, compiled, and loaded an SELinux policy that will allow HAProxy and Rsyslog to communicate over their shared socket.

      In the next step you will test that everything works by making an HTTP request to HAProxy and examining its new log file.

      Step 5 — Testing HAProxy Logging

      Now that you have configured HAProxy, Rsyslog, and optionally SELinux, you can test that logging to /var/log/haproxy.log is working.

      By default the haproxy package ships with a configuration file that creates an HTTP listener socket on port 5000. The configuration points to a non-existent backend server, so any request to the port will result in an HTTP 503 error.

      To check for a 503 error in your /var/log/haproxy.log file, first generate an HTTP request using curl like this:

      • curl -si http://127.0.0.1:5000

      You should receive output like the following:

      Output

      HTTP/1.0 503 Service Unavailable Cache-Control: no-cache Connection: close Content-Type: text/html <html><body><h1>503 Service Unavailable</h1> No server is available to handle this request. </body></html>

      Now examine /var/log/haproxy.log for any HTTP 503 responses using the grep command:

      • sudo grep -E ‘NOSRV.+503’ /var/log/haproxy.log

      Note: The NOSRV.+503 portion of the command is a regular expression. This tutorial on Using Grep & Regular Expressions to Search for Text Patterns in Linux
      goes into more depth on using grep and regular expressions.

      You should receive a line (or multiple lines) like the following:

      [secondary_label Output
      Sep 9 21:32:22 centos-s-1vcpu-1gb-nyc3-01 haproxy[4451]: 127.0.0.1:56024 [9/Sep/2020:21:32:22.098] main app/<NOSRV> 0/-1/-1/-1/0 503 212 - - SC-- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
      

      This line corresponds to the curl request that you made, which means that Rsyslog and HAProxy are configured to use their shared socket correctly.

      Conclusion

      In this quickstart tutorial, you configured HAProxy to log to a Unix domain socket. You also set up Rsyslog to create and read from the socket so that the two programs can communicate with each other without opening up any TCP/IP ports on your system. Finally, you optionally compiled, packaged, and loaded an SELinux policy to allow Rsyslog and HAProxy shared access to their socket.



      Source link

      S.O.L.I.D: The First 5 Principles of Object Oriented Design


      S.O.L.I.D is an acronym for the first five object-oriented design(OOD)** principles** by Robert C. Martin, popularly known as Uncle Bob.

      These principles, when combined together, make it easy for a programmer to develop software that are easy to maintain and extend. They also make it easy for developers to avoid code smells, easily refactor code, and are also a part of the agile or adaptive software development.

      S.O.L.I.D stands for:

      Let’s look at each principle individually to understand why S.O.L.I.D can help make us better developers.

      Single-responsibility Principle

      S.R.P for short – this principle states that:

      A class should have one and only one reason to change, meaning that a class should have only one job.

      For example, say we have some shapes and we wanted to sum all the areas of the shapes. Well this is pretty simple right?

      class Circle {
          public $radius;
      
          public function construct($radius) {
              $this->radius = $radius;
          }
      }
      
      class Square {
          public $length;
      
          public function construct($length) {
              $this->length = $length;
          }
      }
      

      First, we create our shapes classes and have the constructors setup the required parameters. Next, we move on by creating the AreaCalculator class and then write up our logic to sum up the areas of all provided shapes.

      class AreaCalculator {
      
          protected $shapes;
      
          public function __construct($shapes = array()) {
              $this->shapes = $shapes;
          }
      
          public function sum() {
              // logic to sum the areas
          }
      
          public function output() {
              return implode('', array(
                  "",
                      "Sum of the areas of provided shapes: ",
                      $this->sum(),
                  ""
              ));
          }
      }
      

      To use the AreaCalculator class, we simply instantiate the class and pass in an array of shapes, and display the output at the bottom of the page.

      $shapes = array(
          new Circle(2),
          new Square(5),
          new Square(6)
      );
      
      $areas = new AreaCalculator($shapes);
      
      echo $areas->output();
      

      The problem with the output method is that the AreaCalculator handles the logic to output the data. Therefore, what if the user wanted to output the data as json or something else?

      All of that logic would be handled by the AreaCalculator class, this is what SRP frowns against; the AreaCalculator class should only sum the areas of provided shapes, it should not care whether the user wants json or HTML.

      So, to fix this you can create an SumCalculatorOutputter class and use this to handle whatever logic you need to handle how the sum areas of all provided shapes are displayed.

      The SumCalculatorOutputter class would work like this:

      $shapes = array(
          new Circle(2),
          new Square(5),
          new Square(6)
      );
      
      $areas = new AreaCalculator($shapes);
      $output = new SumCalculatorOutputter($areas);
      
      echo $output->JSON();
      echo $output->HAML();
      echo $output->HTML();
      echo $output->JADE();
      

      Now, whatever logic you need to output the data to the user is now handled by the SumCalculatorOutputter class.

      Open-closed Principle

      Objects or entities should be open for extension, but closed for modification.

      This simply means that a class should be easily extendable without modifying the class itself. Let’s take a look at the AreaCalculator class, especially it’s sum method.

      public function sum() {
          foreach($this->shapes as $shape) {
              if(is_a($shape, 'Square')) {
                  $area[] = pow($shape->length, 2);
              } else if(is_a($shape, 'Circle')) {
                  $area[] = pi() * pow($shape->radius, 2);
              }
          }
      
          return array_sum($area);
      }
      

      If we wanted the sum method to be able to sum the areas of more shapes, we would have to add more if/else blocks and that goes against the Open-closed principle.

      A way we can make this sum method better is to remove the logic to calculate the area of each shape out of the sum method and attach it to the shape’s class.

      class Square {
          public $length;
      
          public function __construct($length) {
              $this->length = $length;
          }
      
          public function area() {
              return pow($this->length, 2);
          }
      }
      

      The same thing should be done for the Circle class, an area method should be added. Now, to calculate the sum of any shape provided should be as simple as:

      public function sum() {
          foreach($this->shapes as $shape) {
              $area[] = $shape->area();
          }
      
          return array_sum($area);
      }
      

      Now we can create another shape class and pass it in when calculating the sum without breaking our code. However, now another problem arises, how do we know that the object passed into the AreaCalculator is actually a shape or if the shape has a method named area?

      Coding to an interface is an integral part of S.O.L.I.D, a quick example is we create an interface, that every shape implements:

      interface ShapeInterface {
          public function area();
      }
      
      class Circle implements ShapeInterface {
          public $radius;
      
          public function __construct($radius) {
              $this->radius = $radius;
          }
      
          public function area() {
              return pi() * pow($this->radius, 2);
          }
      }
      

      In our AreaCalculator sum method we can check if the shapes provided are actually instances of the ShapeInterface, otherwise we throw an exception:

      public function sum() {
          foreach($this->shapes as $shape) {
              if(is_a($shape, 'ShapeInterface')) {
                  $area[] = $shape->area();
                  continue;
              }
      
              throw new AreaCalculatorInvalidShapeException;
          }
      
          return array_sum($area);
      }
      

      Liskov substitution principle

      Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

      All this is stating is that every subclass/derived class should be substitutable for their base/parent class.

      Still making use of out AreaCalculator class, say we have a VolumeCalculator class that extends the AreaCalculator class:

      class VolumeCalculator extends AreaCalulator {
          public function construct($shapes = array()) {
              parent::construct($shapes);
          }
      
          public function sum() {
              // logic to calculate the volumes and then return and array of output
              return array($summedData);
          }
      }
      

      In the SumCalculatorOutputter class:

      class SumCalculatorOutputter {
          protected $calculator;
      
          public function __constructor(AreaCalculator $calculator) {
              $this->calculator = $calculator;
          }
      
          public function JSON() {
              $data = array(
                  'sum' => $this->calculator->sum();
              );
      
              return json_encode($data);
          }
      
          public function HTML() {
              return implode('', array(
                  '',
                      'Sum of the areas of provided shapes: ',
                      $this->calculator->sum(),
                  ''
              ));
          }
      }
      

      If we tried to run an example like this:

      $areas = new AreaCalculator($shapes);
      $volumes = new AreaCalculator($solidShapes);
      
      $output = new SumCalculatorOutputter($areas);
      $output2 = new SumCalculatorOutputter($volumes);
      

      The program does not squawk, but when we call the HTML method on the $output2 object we get an E_NOTICE error informing us of an array to string conversion.

      To fix this, instead of returning an array from the VolumeCalculator class sum method, you should simply:

      public function sum() {
          // logic to calculate the volumes and then return and array of output
          return $summedData;
      }
      

      The summed data as a float, double or integer.

      Interface segregation principle

      A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.

      Still using our shapes example, we know that we also have solid shapes, so since we would also want to calculate the volume of the shape, we can add another contract to the ShapeInterface:

      interface ShapeInterface {
          public function area();
          public function volume();
      }
      

      Any shape we create must implement the volume method, but we know that squares are flat shapes and that they do not have volumes, so this interface would force the Square class to implement a method that it has no use of.

      ISP says no to this, instead you could create another interface called SolidShapeInterface that has the volume contract and solid shapes like cubes e.t.c can implement this interface:

      interface ShapeInterface {
          public function area();
      }
      
      interface SolidShapeInterface {
          public function volume();
      }
      
      class Cuboid implements ShapeInterface, SolidShapeInterface {
          public function area() {
              // calculate the surface area of the cuboid
          }
      
          public function volume() {
              // calculate the volume of the cuboid
          }
      }
      

      This is a much better approach, but a pitfall to watch out for is when type-hinting these interfaces, instead of using a ShapeInterface or a SolidShapeInterface.

      You can create another interface, maybe ManageShapeInterface, and implement it on both the flat and solid shapes, this way you can easily see that it has a single API for managing the shapes. For example:

      interface ManageShapeInterface {
          public function calculate();
      }
      
      class Square implements ShapeInterface, ManageShapeInterface {
          public function area() { /Do stuff here/ }
      
          public function calculate() {
              return $this->area();
          }
      }
      
      class Cuboid implements ShapeInterface, SolidShapeInterface, ManageShapeInterface {
          public function area() { /Do stuff here/ }
          public function volume() { /Do stuff here/ }
      
          public function calculate() {
              return $this->area() + $this->volume();
          }
      }
      

      Now in AreaCalculator class, we can easily replace the call to the area method with calculate and also check if the object is an instance of the ManageShapeInterface and not the ShapeInterface.

      Dependency Inversion principle

      The last, but definitely not the least states that:

      Entities must depend on abstractions not on concretions. It states that the high level module must not depend on the low level module, but they should depend on abstractions.

      This might sound bloated, but it is really easy to understand. This principle allows for decoupling, an example that seems like the best way to explain this principle:

      class PasswordReminder {
          private $dbConnection;
      
          public function __construct(MySQLConnection $dbConnection) {
              $this->dbConnection = $dbConnection;
          }
      }
      

      First the MySQLConnection is the low level module while the PasswordReminder is high level, but according to the definition of D in S.O.L.I.D. which states that Depend on Abstraction not on concretions, this snippet above violates this principle as the PasswordReminder class is being forced to depend on the MySQLConnection class.

      Later if you were to change the database engine, you would also have to edit the PasswordReminder class and thus violates Open-close principle.

      The PasswordReminder class should not care what database your application uses, to fix this again we “code to an interface”, since high level and low level modules should depend on abstraction, we can create an interface:

      interface DBConnectionInterface {
          public function connect();
      }
      

      The interface has a connect method and the MySQLConnection class implements this interface, also instead of directly type-hinting MySQLConnection class in the constructor of the PasswordReminder, we instead type-hint the interface and no matter the type of database your application uses, the PasswordReminder class can easily connect to the database without any problems and OCP is not violated.

      class MySQLConnection implements DBConnectionInterface {
          public function connect() {
              return "Database connection";
          }
      }
      
      class PasswordReminder {
          private $dbConnection;
      
          public function __construct(DBConnectionInterface $dbConnection) {
              $this->dbConnection = $dbConnection;
          }
      }
      

      According to the little snippet above, you can now see that both the high level and low level modules depend on abstraction.

      Conclusion

      S.O.L.I.D might seem to be a bit too abstract at first, but with each real-world application of S.O.L.I.D. principles, the benefits of adherence to its guidelines will become more apparent. Code that follows S.O.L.I.D. principles can more easily be shared with collaborators, extended, modified, tested, and refactored without any problems.



      Source link

      How to Use the JavaScript Fetch API to Get Data


      We all remember the dreaded XMLHttpRequest we used back in the day to make requests, it involved some really messy code, it didn’t give us promises and let’s just be honest, it wasn’t pretty JavaScript, right? Maybe if you were using jQuery, you used the cleaner syntax with jQuery.ajax().

      Well JavaScript has it’s own built-in clean way now. Along comes the Fetch API a new standard to make server request jam-packed with promises and all those things we learned to love over the years.

      How do we use the Fetch API?

      In a very simple manner all you really do is call fetch with the URL you want, by default the Fetch API uses the GET method, so a very simple call would be like this:

      fetch(url) // Call the fetch function passing the url of the API as a parameter
      .then(function() {
          // Your code for handling the data you get from the API
      })
      .catch(function() {
          // This is where you run code if the server returns any errors
      });
      

      Looks pretty simple right? So let’s starting using it…

      We are now going to build a simple GET request, in this case, I will use the Random User API and we will get 10 users and show them on the page using vanilla JavaScript.

      Let’s get started with the HTML, all we really need is a heading and an unordered list:

        <h1>Authors</h1>
        <ul id="authors"></ul>
      

      The idea is to get all the data from the Random User API and display it in list items inside the author’s list.

      The first step is to actually set the URL we need and also the list we are gonna put the data in, so in the Javascript we write:

        const ul = document.getElementById('authors'); // Get the list where we will place our authors
        const url="https://randomuser.me/api/?results=10"; // Get 10 random users
      

      I have set these to consts so you don’t risk changing these in the future and these two are meant to be constants through all the project.
      Now we get into actual Fetch API:

      fetch(url)
        .then(function(data) {
          // Here you get the data to modify as you please
          })
        })
        .catch(function(error) {
          // If there is any error you will catch them here
        });   
      

      Let’s review this code, shall we?
      So first we are calling the Fetch API and passing it the URL we defined as a constant above and since no more parameters are set this is a simple GET request.
      Then we get a response but the response we get is not JSON but an object with a series of methods we can use depending on what we want to do with the information, these methods include:

      • clone() – As the method implies this method creates a clone of the response.
      • redirect() – This method creates a new response but with a different URL.
      • arrayBuffer() – In here we return a promise that resolves with an ArrayBuffer.
      • formData() – Also returns a promise but one that resolves with FormData object.
      • blob() – This is one resolves with a Blob.
      • text() – In this case it resolves with a string.
      • json() – Lastly we have the method to that resolves the promise with JSON.

      Looking at all these methods the one we want is the JSON one because we want to handle our data as a JSON object so we add:

        fetch(url)
        .then((resp) => resp.json()) // Transform the data into json
        .then(function(data) {
          // Create and append the li's to the ul
          })
        })
      

      Now let’s get to the part we create the list items, for that, I created two helper functions at the top of my file just to make the code simpler down the line:

        function createNode(element) {
          return document.createElement(element); // Create the type of element you pass in the parameters
        }
      
        function append(parent, el) {
          return parent.appendChild(el); // Append the second parameter(element) to the first one
        }
      

      All these functions do is append and create elements as you can see.
      Once this is done we can move on to the resolution of our promise and add the code we need to append these list items to our unordered list:

      then(function(data) {
          let authors = data.results; // Get the results
          return authors.map(function(author) { // Map through the results and for each run the code below
            let li = createNode('li'), //  Create the elements we need
                img = createNode('img'),
                span = createNode('span');
            img.src = author.picture.medium;  // Add the source of the image to be the src of the img element
            span.innerHTML = `${author.name.first} ${author.name.last}`; // Make the HTML of our span to be the first and last name of our author
            append(li, img); // Append all our elements
            append(li, span);
            append(ul, li);
          })
        })
      

      So first we define authors as the response we get from the request then we map over all the authors and for each we create a list item, a span, and an image.
      In the image source, we place the picture of the user, the HTML of the span will be the first and last name interpolated and then all we need to do is append this to their rightful parents and voilá, our HTTP request in vanilla JavaScript is done and returning something to the HTML.

      To handle our catch all I will do is console log the error as we get it but you can do whatever you want with the error such as append it to the HTML with the functions we created. This is the full code of our little request:

        function createNode(element) {
            return document.createElement(element);
        }
      
        function append(parent, el) {
          return parent.appendChild(el);
        }
      
        const ul = document.getElementById('authors');
        const url="https://randomuser.me/api/?results=10";
        fetch(url)
        .then((resp) => resp.json())
        .then(function(data) {
          let authors = data.results;
          return authors.map(function(author) {
            let li = createNode('li'),
                img = createNode('img'),
                span = createNode('span');
            img.src = author.picture.medium;
            span.innerHTML = `${author.name.first} ${author.name.last}`;
            append(li, img);
            append(li, span);
            append(ul, li);
          })
        })
        .catch(function(error) {
          console.log(error);
        });   
      

      Handling more requests like POST

      So this is a GET request, the default one for the fetch function but of course we can do all other types of requests and also change the headers and off course send data, all we need for this is to set our object and pass it as the second argument of the fetch function:

      const url="https://randomuser.me/api";
      // The data we are going to send in our request
      let data = {
          name: 'Sara'
      }
      // The parameters we are gonna pass to the fetch function
      let fetchData = { 
          method: 'POST', 
          body: data,
          headers: new Headers()
      }
      fetch(url, fetchData)
      .then(function() {
          // Handle response you get from the server
      });
      

      You can also define cache, mode and all those things you are used to defining in your POST requests.

      To create our object and use the fetch function we also have another option and that is to use the request constructor to create our request object, so instead of defining the object in the function itself we do this:

      const url="https://randomuser.me/api";
      // The data we are going to send in our request
      let data = {
          name: 'Sara'
      }
      // Create our request constructor with all the parameters we need
      var request = new Request(url, {
          method: 'POST', 
          body: data, 
          headers: new Headers()
      });
      
      fetch(request)
      .then(function() {
          // Handle response we get from the API
      })
      

      You can use the way you are most comfortable with to build your request object.

      While the Fetch API is not yet supported by all the browsers (currently Safari does not support it) it is the beautiful replacement for XMLHttpRequest we desperately needed in our life.
      There are also polyfills if you really want to use it in more professional projects.



      Source link