Programming

Express – Attaching Data to a Proxy Router

Problem

Forward an HTTP request from one server to another while attaching data to the request. In the example the user will select a file from the Portal-Server and attach the file data on a request to the API-Server. We will also attach the user identification that was returned from the Authentication-Server.

Encoding Type

When you make a POST request, you have to encode the data that forms the body of the request in some way [1]. This encoding type is communicated in the header field enctype, which has one of three values.

  • application/x-www-form-urlencoded – A URL encoded form, this is the default.
  • multipart/form-data – Multipart form with fields separated by a unique value. Used for uploading files.
  • text/plain – Sends the data without any encoding.

We will only concern ourselves with the first two encoding types.

Encoding: application/x-www-form-urlencoded

Submit the POST with the parameters encoded in the body as a string of key-value pairs. Each pair is separated by an ‘&’ and the pair is seperated by ‘=’.

key1=value1&key2=value2

The key value pairs are inserted into the body and can be extracted by the server with the bodyParser package. The key-value pairs can also be directly attached to the URL request, in which case they are retrieved from the req.query field. See implementation.

Encoding: multipart/form-data

Generally used for submitting file, or binary data. We will highlight the differences by manipulating the first example. We will keep everything else the same except for the encoding type. To retrieve the data on the server we will use the multer package.

The only difference in the multipart request as opposed to the urlencoded request is the in the body.

------WebKitFormBoundaryB2AGWn1yYcOCi0T9
Content-Disposition: form-data; name="jobid"

0
------WebKitFormBoundaryB2AGWn1yYcOCi0T9
Content-Disposition: form-data; name="jobname"

sdf
------WebKitFormBoundaryB2AGWn1yYcOCi0T9--

Instead of the ampersand delimited string of parameters, the parameters are now delimited by the unique boundary string “——WebKitFormBoundaryB2AGWn1yYcOCi0T9“. The boundary string is chosen in such a way as it does not appear anywhere else in the body data. Normally the programmer does not need to worry themselves about it, it is generated automatically. The boundary string is send in the HTTP content-type request header, appended to the type by a semicolon. See implementation.

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryB2AGWn1yYcOCi0T9

Attaching a file to a form

The simplest way to add a file is to include the file-input HTML tag on the form.

<input id="upload-dialog" type="file" name="fileupload">

The raw file is added to the body preceded by some file information. This is similar to how data fields get appended to the body.

------WebKitFormBoundaryvI9zg1SrRVBgB6yG
Content-Disposition: form-data; name="fileupload"; filename="iama_text_file.txt"
Content-Type: text/plain

In here is some simple text.
Two lines to fancy it up a bit.

------WebKitFormBoundaryvI9zg1SrRVBgB6yG--

To retrieve the file on the server we will again use the multer package, except this time we’ll use the ‘single‘ method. This will place the file in the ‘uploads‘ subdirectory. Note, the parameter we pass matches the name we gave to the upload-dialog above.

multer({ dest: 'uploads/' }).single('fileupload')

If you now examine the uploads/ subdirectory, you’ll find a file with a hash file for a name. This is because do not want to save raw filenames to your file-system. The uploaded file information is stored in the req.file field (see below). You can file the saved filename as well as the original filename here. You can sanitize the filename and move or copy the file as you see fit.

fieldname: 'fileupload',
originalname: 'iama_text_file.txt',
encoding: '7bit',
mimetype: 'text/plain',
destination: 'uploads/',
filename: '5f66c866e68790a2c494275829acd0b8',
path: 'uploads/5f66c866e68790a2c494275829acd0b8',
size: 61

Attaching File Data to a Proxy

Now that we understand the formatting of a file-upload, we want to manipulate a HTTP request to dynamically attach file data. The situation is that a file was previously uploaded and stored on the Portal-Server. Now we want to send that file to an API-Server with whatever other information is required. Doing this allows us to test the API-Server without special consideration, simply by using a web form (as we’ve been doing). Then when we deploy the API-Server we firewall it so that only the Portal-Server sees it. But, we proxy the call from the Portal-Server to the API-Server.

To facilitate proxies we are going to use the http-proxy-middleware package. To view the full code visit the github repository for this project.

Proxy Request

The basic proxy requires a options object. The one we’ll be using is presented below. There are many more options available in the documentation.

  • target – Target host name.
  • pathRewrite – Regex rewrite of the target’s url path.
  • onProxyReq – The function to manipulate the request.
{    
    target: "http://localhost:8000",
    pathRewrite: { 'forward': 'raw' },

    onProxyReq: appendFile
}

We will only be attaching the proxy to a single url. We call ‘multer’ first to extract existing parameters. Then we call the proxy, passing in the options object. This is all that is required for a simple proxy. What remains is attaching the file and data information to the request.

router.post("/forward",
    multer({}).none(),
    createProxyMiddleware(proxyOptions),
);

Attach Data to Request Body

The onProxyReq function accepts three parameters: proxyReq, req, and res. We will only be concerned with the first two.

function appendFile(proxyReq, req, res) {
    const args = {
        ...req.body,
        userid: "user@id"
    }

    const filepath = Path.join(".", "uploads", req.body.filename);
    const formData = new FormData();

    Object.keys(args).map(key => formData.append(key, args[key]));
    formData.append('fileupload', FS.readFileSync(filepath), 'file.txt');

    proxyReq.setHeader('content-type', `multipart/form-data; boundary=${formData.getBoundary()}`);
    proxyReq.setHeader('content-length', formData.getBuffer().length);

    proxyReq.write(formData.getBuffer().toString("utf-8"));
    proxyReq.end();
}

(8) The FormData class is a library to create readable "multipart/form-data" streams. Can be used to submit forms and file uploads to other web applications.

(10) We add all values to the FormData object. We preserve old values (3) and append new values (4). This is optional and can be omitted if you want to remove the previous parameters.

(11) The file data is added to the FormData object using the readFileSync function which returns a Buffer object.

(13) We manually set the ready on the proxy request particularly to specify the boundary string.

(14) The FormData object will also provide us with the buffer length.

(16) The buffered FormData object is written out to proxyReq.

External References

[1] https://www.w3.org/html/wg/spec/association-of-controls-and-forms.html#attr-fs-enctype

[2] https://nodejs.org/api/http.html#http_class_http_clientrequest

https://dev.to/sidthesloth92/understanding-html-form-encoding-url-encoded-and-multipart-forms-3lpa

https://stackoverflow.com/questions/37630419/how-to-handle-formdata-from-express-4

https://stackoverflow.com/questions/64803772/nodejs-fileupload-multer-vs-express-fileupload-which-to-use

https://stackoverflow.com/questions/72809300/how-to-add-user-data-in-http-proxy-middleware

MySQL Server on Ubuntu 22.04 (WSL)

Install and Setup

sudo apt update
sudo apt upgrade
sudo apt install mysql-server
sudo service mysql start
sudo service mysql status
sudo mysql
sql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password by 'password';
sudo mysql_secure_installation

Add User

mysql -u root -p
sql> CREATE USER 'MyUsername'@'localhost' IDENTIFIED BY 'password';
sql> CREATE DATABASE datebase_name;
sql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, DROP, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON database_name.* TO 'MyUsername'@'localhost';
sql> quit

Access Database

mysql -u MyUsername -p
sql> show databases;
sql> use database_name;

C# JSON Client-Server Implementation

Background

A Socket is one end of a two way communication link. The major protocols for socket communication are TCP and UDP. The primary difference is that TCP guarantees data delivery and the order of data packets. UDP does not make such guarantees, but as a consequence it is faster. Developers typically default to TCP.

The C# socket library is found inthe System.Net package.

using System.Net;
using System.Net.Sockets;

IP Endpoint

An IPEndpoint is the pairing of an IPAddress and a port number. You can use DNS lookup to obtain an IP address.

IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("google.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint ipEndPoint = new(ipAddress, 7000);

When creating the server endpoint, you can specify ‘any’ for the ip address.

IPEndPoint ipEndPoint = new(IPAddress.Any, 7000);

Terminate a Socket

Shutdown disables sends and receives on a Socket.

Close will terminate the Socket connection and releases all associated resources.

socket.Shutdown(SocketShutdown.Both);
socket.Close();

Server

A server must first listen for and accept connections. Then, when a connection is made, listen for data on a seperate Socket.

Creating a Socket

Bind associates a socket with an endpoint.

Listen causes a connection-oriented (server) Socket to listen for incoming connection attempts.

Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(ipEndPoint);
socket.Listen(port);

Accepting Connections

Accept blocks until an incoming connection attempt is queued then it extracts the first pending request from a queue. It then creates and returns a new Socket. You can call the RemoteEndPoint method of the returned Socket to identify the remote host’s network address and port number.

See AcceptAsync for the asynchronous accept call.

Socket handler = this.listener.Accept();

Client

You create the client side socket in the same manner as the server. The difference being, instead of Bind and Listen, the client uses Connect.

IPAddress ipAdd = IPAddress.Parse("127.0.0.1");
IPEndPoint ipEndPt = new IPEndPoint(ipAdd, port);
Socket socket = new Socket(ipAdd.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(ipEndPt);

Reading & Writing

Reading and writing date on a socket is the same for both the server and client side socket. Once the actual connection is made, the difference between the two is arbitrary. Both the read and operations require a byte array to act as a buffer. So your data will need to be converted to and from an array of bytes.

Write

The Send method is used to write to a socket. While there are a number of different method flavours, the most common is to send the entire contents of a byte array.

byte[] msg = Encoding.ASCII.GetBytes(aString);
this.socket.Send(msg);

If you are using a connection-oriented protocol, Send will block until the requested number of bytes are sent, unless a time-out was set by using Socket.SendTimeout. If the time-out value was exceeded, the Send call will throw a SocketException

If the receiving socket (server) has not been started a SocketException will be thrown. If the server has been started but Accept has not been called reading and writing will hang. This can be remedied by setting a timeout (in ms).

Set Read/Write Timeouts

socket.ReceiveTimeout = 1000;
scoket.SentTimeout = 1000;

Read

If the remote host shuts down the Socket connection with the Shutdown method, and all available data has been received, the Receive method will complete immediately and return zero bytes. This allows you to detect a clean shutdown.

There are many flavours of the Receive method, but we will only concern ourselves with two of them. The first reads all bytes from a socket. It returns the number of bytes read. You will need to provide your own EOF indicator or wait until 0 bytes are read which means the socket has finished writing and closed. This is useful if you are only connecting the sockets for a single read-write operation.

byte[] bytes = new byte[BUFFER_SIZE];
socket.Receive(bytes);

The second, and the one we will be using, is to read a specific number of bytes from the socket.

int count = socket.Receive(bytes, nextReadSize, SocketFlags.None);

We will use this to first read the size of the data, then read the body of the data.

// read size
byte[] bytes = new byte[INT_BUFFER_SIZE];
socket.Receive(bytes, INT_BUFFER_SIZE, SocketFlags.None);
int size = BitConverter.ToInt32(bytes, 0);

// read message
byte[] bytes = new byte[size];
socket.Receive(bytes, size, SocketFlags.None)
string data = Encoding.ASCII.GetString(bytes, 0, count);

Implementation

JSON

The json encoding and decoding will be handled by the Newtonsoft Json.Net library.

dotnet add package Newtonsoft.Json --version 13.0.1

The include statements for this library are.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Layout

There are 3 source files in the project:

  • Server.cs: Manages new client connecting to the server and emits Connection objects.
  • Connection.cs: Wraps a socket providing the read-write capabilities.
  • Client.cs: A connection object that connects to its own socket.

Server

Most of the details for this class are provided in the above background section. The main detail here is the way new connections are managed. I have put the loop inside a IEnumerable method. This should be looped to hand off new connection objects. The following example does this in a new thread.

Thread thread = new Thread(new ThreadStart(()=>{    
    foreach(Connection connection in server.Connections()){
        connection.WriteString("ack");
        connection.Close();
    }
}));

Connection

The connection object reads and writes JSON objects or Strings in two parts. First it writes four bytes representing the integer size of the data. Next it writes the data it’s self.

Client client = new Client().Connect("127.0.0.1", 7000);
client.socket.ReceiveTimeout = 3000;
Console.WriteLine("> " + client.ReadString());
server.Stop();

C# Creating a Class Library

This instruction post is an abbreviation of Microsoft tutorial found HERE.

Contents

  • Create the Library
  • Using the Library
  • Unit Testing
  • Including the .dss Directly
  • Setup

    Create an SLN (project solution file)

    An SLN is a Visual Studio solution file that keeps information about the organization of projects in a file.

    dotnet new sln

    Create the class library project

    The -o (–output) flag specifies the directory to place the project. It will create the directory.

    dotnet new classlib -o StringLibrary

    Add the project to the solution

    dotnet sln add StringLibrary/StringLibrary.csproj

    Add code to the library

    Add the following code to the StringLibrary/Class1.cs file

    namespace UtilityLibraries;
    
    public static class StringLibrary
    {
        public static bool StartsWithUpper(this string? str)
        {
            if (string.IsNullOrWhiteSpace(str))
                return false;
    
            char ch = str[0];
            return char.IsUpper(ch);
        }
    }

    Build the solution

    dotnet build

    Using the Library

    Create a console app

    dotnet new console -o ShowCase

    Add the console app to the solution

    dotnet sln add ShowCase/ShowCase.csproj

    Modify the ShowCase/Program.cs file

    using UtilityLibraries;
    
    class Program {
        static void Main(string[] args) {
            foreach(string s in args){
                var uc = s.StartsWithUpper();
                Console.WriteLine($"String {s} starts with uppser case {uc}");
            }
        }
    }

    Add a ‘StringLibrary’ reference to ‘ShowCase’

    dotnet add ShowCase/ShowCase.csproj reference StringLibrary/StringLibrary.csproj

    Run ‘ShowCase’

    dotnet run --project ShowCase/ShowCase.csproj AB ab

    Unit Testing

    Create the unit test project and add a reference

    dotnet new mstest -o StringLibraryTest
    dotnet add StringLibraryTest/StringLibraryTest.csproj reference StringLibrary/StringLibrary.csproj

    Run the unit test

    dotnet test StringLibraryTest/StringLibraryTest.csproj

    Including the .dll directly

    Add the following to your project’s .csproj file. Change the StringLibrary project name as necessary.

      <ItemGroup>
        <Reference Include="StringLibrary">
          <HintPath>./lib/StringLibrary.dll</HintPath>
        </Reference>
      </ItemGroup>

    Installing C# on Ubuntu Linux

  • Background
  • Install the SDK
  • Install the Runtime
  • Create an Application
  • External Reading
  • Background

    The .NET framework was main implementation of the open specification, Common Language Infrastructure (CLI) which is platform independent target for high level languages.

    The Common Language Runtime (CLR) is synonymous to Java’s JRE. Compilers target the CLR and emit managed code. This code permits cross-language integration and exception handling.

    The .NET framework was cross-platform by design only, not on implementation. Until about 2012, the .NET Runtime (CLR) was for Windows, and the .Net Framework was Windows-centric. There was limited cross-platform support from the Mono project, being unsupported, it lagged behind the official releases.

    In June of 2016, Microsoft released .NET Core. This was a port of the CLR to Linux with a rewriting of the framework from the ground up. Version 2.0 was released in 2017, and version 3.0 in 2019. This numbering system conflicted with the Windows proprietary .NET framework numbering. As such, version 4 was skipped and .NET 5.0 was released in 2020 followed by .NET 6.0 in 2021.

    As of version 5.0, the old .NET Framework was given “legacy support” status, and .NET Core was renamed to just .NET. This is why the confusion really gets bad.

    In summary, .NET Framework had versions 1.0, 1.1, 2.0, 3.0, 3.5, and 4.0-4.8. It will never have a 5.0 version. While .NET Core had versions 1.0, 2.0, 2.1, 3.0, and 3.1. Then it was renamed, to just .NET 5.

    Future versions of .NET will provide LTS for even-numbered versions. New versions release every November.

    This image is licensed under the Creative Commons Attribution-Share Alike 4.0 International license.

    Source https://en.wikipedia.org/wiki/File:Overview_of_the_Common_Language_Infrastructure_2015.svg

    Install the SDK

    source

    The .NET SDK allows you to develop apps with .NET. If you install the .NET SDK, you don’t need to install the runtime.

    sudo apt-get update
    sudo apt-get install -y dotnet6

    Install the runtime (optional)

    The ASP.NET Core Runtime allows you to run apps that were made with .NET that didn’t provide the runtime.

    sudo apt-get update
    sudo apt-get install -y aspnetcore-runtime-6.0

    You can install the .NET Runtime, which doesn’t include ASP.NET Core.

    sudo apt-get update
    sudo apt-get install -y dotnet-runtime-6.0

    Create an application

    Create a new .NET application called “HelloWorld”

    1. Open your terminal
    2. Create and enter a new directory called “HelloWorld”
    3. Use the “dotnet” command to create a new application.
    dotnet new console --framework net6.0

    In your IDE, replace the contents of Program.cs with the following code:

    namespace HelloWorld {
        class Program {
            static void Main(string[] args) {
                Console.WriteLine("Hello World!");
            }
        }
    }

    Alternatively you can create a new console app with the directory structure by including the output flag (-o).

    dotnet new console -o HelloWorld

    External Reading

    Create a Class Library

    Summation of a Partial Beatty Sequence

     \sum_{i=1}^{n}\mathcal{B}_{r\in\mathbb{Z}}^i = \sum_{i=1}^{n}\lfloor ir \rfloor

    A partial Beatty sequence is the sum of all the Integer portions of positive multiples between 1 and n of an Irrational number.

    Raleigh-Beatty Theorem

    Rayleigh’s theorem, states that a sequence, consisting of all the positive integers that are not in the sequence, is itself a Beatty sequence generated by a different irrational number.

    For any Beatty \mathcal{B}_r^n sequence where r > 1 there exists a complementary sequence \mathcal{B}_s^n where s>1 is defined as \frac{1}{r}+\frac{1}{s}=1. The union of both sequences is the set of all Integers from 1 to n.

    We can find an equation for s in terms of r.

    \begin{align}
    \begin{gather*}
    \frac{1}{s} + \frac{1}{r} = 1 \\ \downarrow \\
    \frac{1}{s} = 1 - \frac{1}{r} \\ \downarrow \\
    \frac{1}{s} = \frac{r}{r} - \frac{1}{r} \\ \downarrow \\
    \frac{1}{s} = \frac{r-1}{r} \\ \downarrow \\
    s = \frac{r}{r-1}
    \end{gather*}
    \end{align}

    The complement consists of all integers not in the sequence. For any sequence dividing the value by the equation will give you the index of the value that is less than N = \mathcal{B}_r^n. This allows us to find the last index of the complement because the last value of the complement is lower than that of the sequence. \lfloor \frac{\mathcal{B}_r^i}{s} \rfloor.

    \lfloor ir \rfloor = \mathcal{B}_r^i \rightarrow \lfloor \frac{\mathcal{B}_r^i}{r} \rfloor = i-1
    

    \mathcal{B}_r^n is the largest term in the sequence, dividing that by ‘r’ would give you the index of the sequence .

    For clarity we will express the largest value of the sequence as N = \mathcal{B}_r^i , and the upper index of the complement as m = \lfloor N/s \rfloor.

    \begin{align}
    \begin{gather*}
    N = \mathcal{B}_r^n\\ \\
    m = \lfloor N/s \rfloor\\ \\
    \sum_{i=1}^{n}\lfloor ir \rfloor + 
    \sum_{j=1}^m
    \lfloor js \rfloor = 
    \frac{N^2 + N}{2}
    \end{gather*}
    \end{align}

    Building up to the Solution

    Since the union of any Beatty sequence and its complement will cover all the positive integers up to n. This leads to the realization that any Integer between 1 and n can be expressed at either \lfloor ir \rfloor or \lfloor j(r+1) \rfloor.

    The sum of the sequence in question:

    \begin{align}
    S(n, r) = \sum_{i=1}^{n}\mathcal{B}_r^i
    \end{align}

    The sum of the complementary sequence:

    \begin{align}
    S(m, s) = \sum_{j=1}^{m}\mathcal{B}_s^j | s = \frac{r}{r-1}
    \end{align}

    By way of the Raleigh-Beatty Theorem, the two sums are equivalent to the sum of the Integers from 1 to the highest Beatty sequence value (\mathcal{B}_r^n). This can be rearrange to say; the sum of a Beaty sequence is equal to the difference sum of all the encompassed Integers and the sequence’s complement.

    \begin{align}
    \begin{gather*}
    S(n,r) + S(n,s) =
    \sum_{i=1}^Ni \\
    \downarrow \\
    S(n,r) = 
    \sum_{i=1}^Ni - 
    S(n,s) 
    
    \end{gather*}
    \end{align}

    At this point we want to express the sum of the complement in terms of sum the original sequence. We will do this by extracting \mathcal{B}_r^i from \mathcal{B}_i^s. So we need to define a value for s, we will use \sqrt{2}.

    Expressing the Complement in Terms of the Original

    Starting with r and s (1), we assign a value to r.

    \begin{align}
    r = \sqrt{2}, s= \frac{r}{r-1}
    \end{align}

    Substitute r for \sqrt{2}

    \begin{align}
    s= \frac{\sqrt{2}}{\sqrt{2}-1}
    \end{align}

    Factor out \sqrt{2}

    \begin{align}
    \begin{gather*}
    s= 1-\frac{\sqrt{2}(\sqrt{2}+1)}{(\sqrt{2}-1)(\sqrt{2}+1)} \\
    \downarrow\\
    s= \frac{2+\sqrt{2}}{1} \\
    \downarrow\\
     s=2+\sqrt{2}
    \end{gather*}
    \end{align}

    Whole integers can be brought out of the floor function.

    \begin{align}
    \begin{gather*}
    \mathcal{B}_s^i = \lfloor is\rfloor=\lfloor i(2+\sqrt{2})\rfloor \\
    \downarrow \\
    \lfloor 2i+i\sqrt{2}\rfloor \\
    \downarrow \\
    2i+\lfloor i\sqrt{2}\rfloor \\
    \end{gather*}
    \end{align}

    Express \mathcal{B}_s^i in terms of \mathcal{B}_r^i.

    \begin{align}
    \begin{gather*}
    \text{since } \mathcal{B}_r^i=\lfloor i\sqrt{2}\rfloor \\ 
    \downarrow\\
    \mathcal{B}_s^i = \mathcal{B}_r^i + 2i
    \end{gather*}
    \end{align}

    Recursive Function

    Express the complement sum in terms of the original sum.

    \begin{align}
    \begin{gather*}
    S(n,s)=
    \sum_{i=1}^n \mathcal{B}_s^i\\
    \downarrow\\
    \sum_{i=1}^n(\mathcal{B}_r^i+2i) \\
    \downarrow\\
    \sum_{i=1}^n\mathcal{B}_r^i+\sum_{i=1}^n2i \\
    \downarrow\\
    \sum_{i=1}^n\mathcal{B}_r^i+2\sum_{i=1}^ni \\
    \downarrow\\
    S(n, r) + 2(\frac{n(n+1)}{2}) \\
    \downarrow\\
    S(n,s)=S(n, r) + n(n+1)
    
    \end{gather*}
    \end{align}

    From (5) using (11)

    \begin{align}
    \begin{gather*}
    S(n,r) =  
    \sum_{i=1}^Ni -
    S(m,s) \\
    \downarrow \\
    S(n,r) = 
    \sum_{i=1}^Ni - S(m,r) - m(m-1)
    \end{gather*}
    \end{align}

    Expressed as a recursive function.

    \mathcal{S}(n, r)= 
    \begin{cases}
    0 \leq n \leq 1 & 1 \\
    n > 1 & 
    \sum_{i=1}^Ni - S(m,r) - m(m-1)
    \end{cases}

    External References

    https://en.wikipedia.org/wiki/Beatty_sequence

    phpMyAdmin

    About

    The phpMyAdmin tool is an SQL database manager written in PHP. It resides in the web root on your server, making it easily accessed from a web browser.

    There are a number important security considerations when using software like phpMyAdmin, since it:

    • Communicates directly with your MySQL installation.
    • Handles authentication using MySQL credentials.
    • Executes and returns results for arbitrary SQL queries.

    The phpMyAdmin tool is available for a number of different operating systems. This article will focus solely on the Linux installation.

    Manual Installation

    While there are a number of ways to install phpMyAdmin, there is an easy quick install method. This involves downloading and extracting it directly to your web root directory. While you will need at least basic bash terminal knowledge, it is relatively trivial to set up. However, you will require sudo privileges or access to the web-user-account.

    Following are the condensed steps for the quick install found in the phpMyAdmin documentation.

    cd /www
    sudo wget https://files.phpmyadmin.net/phpMyAdmin/5.1.1/phpMyAdmin-5.1.1-english.tar.gz -O phpMyAdmin.tar.gz
    sudo tar -xvf phpMyAdmin.tar.gz
    sudo chown -R wp-user:wp-user phpMyAdmin-5.1.1-english/
    sudo cp config.sample.inc.php config.inc.php
    
    1. Change directory to your web root.
    2. Download the tar file. You can choose from a number of options here. We rename it here with the -O flag
    3. Unpack the tar, it will be placed in it’s own directory.
    4. Give ownership of the directory to the web user account.
    5. Create the configuration file by copying the sample configuration file.

    Usage

    When a user logs into phpMyAdmin the username and password are sent directly to the SQL database. It is just an interface to the database, and any operation it does can be done on the command line. As such, all users must be valid database users.

    Securing

    You should use cookie mode authentication so that your user/password pair are not kept in the configuration file. The variable may be set in the example config file as:

    $cfg['Servers'][$i]['auth_type'] = 'cookie';

    You will need to also add a 'blowfish secret' value to the config file.

    $cfg['blowfish_secret'] = 'anyrandomtextyouwant';

    Deny access to the temp, libraries, and templates subdirectories. Put the following in the server directive of your nginx enabled sites file.

    location /phpmyadmin/libraries { deny all; }
    location /phpmyadmin/templates { deny all; }
    location /phpmyadmin/tmp { deny all; }

    References

    https://www.phpmyadmin.net/

    https://docs.phpmyadmin.net/en/latest

    NodeJS Code Coverage With C8

    Code coverage allows you to determine how well unit-tests cover your codebase. The standard code coverage suite for JavaScript is the NYC interface for Istanbul. However, this package doesn’t play well with NodeJS modules. Instead we are going to use the C8 package. C8 uses functionality native to NodeJS to create output that is compatible with Istanbul reporters.

    Installation

    npm i --save-dev c8

    Execution

    You can run c8 on any source file by simply passing in the appropriate node command.

    $ npx c8 node src/index.js
    -----------|---------|----------|---------|---------|-------------------
    File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
    -----------|---------|----------|---------|---------|-------------------
    All files  |   88.23 |      100 |      50 |   88.23 |
     MooYou.js |   84.61 |      100 |      50 |   84.61 | 9-10
     index.js  |     100 |      100 |     100 |     100 |
    -----------|---------|----------|---------|---------|-------------------

    You can also easily run it with your Mocha test files.

    $ npx c8 mocha
      MooYou Class Test
        #who
          ✔ returns 'you'
    
    
      1 passing (3ms)
    
    -----------|---------|----------|---------|---------|-------------------
    File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
    -----------|---------|----------|---------|---------|-------------------
    All files  |   84.61 |      100 |      50 |   84.61 |
     MooYou.js |   84.61 |      100 |      50 |   84.61 | 9-10
    -----------|---------|----------|---------|---------|-------------------

    Configuration

    C8 can be configured from the command line, in your package.json file, or in a configuration file in your project directory. In particular you can instruct C8 to report one files as opposed to just those in your tests (npx c8 --all mocha). You can also specify which files to include and/or ignore. See the configuration documentation for specific details.

    Generating Reports

    By default the coverage data will be located in the coverage/tmp directory. You can change this directory with the --temp-directory flag. Don’t forget to add this to your .gitignore file.

    Run npx c8 report to regenerate reports after c8 has already been run. Use the -r flag to specify which reporter to use, and the -o flag to specify where to output the report. By default generated reports can be found in the coverage/ dirctory.

    Vanilla C8 command with HTML report:

    npx c8 -r html mocha

    Generate C8 report after the fact:

    npx c8 -r html report

    Two-part C8 command with custom directories and HTML report:

    npx c8 --temp-directory .c8 mocha
    npx c8 report --temp-directory .c8 -o html_coverage -r html

    In Code Markup

    You can add comments into your source code that tells c8 to ignore certain portions of code.

    Ignoring all lines until told to stop.

    /* c8 ignore start */
    function foo() {
      ...
    }
    /* c8 ignore stop */

    Ignore next line.

    /* c8 ignore next */

    External References

    https://www.npmjs.com/package/c8

    https://github.com/bcoe/c8

    Git: Checkout a Single File from a Previous Commit

    Often we alter or delete a file that we didn’t really want to. Sometimes this goes unnoticed for a few commits. It’s fairly easy in git to retrieve a specific file from a specific commit.

    git checkout <commit> <path>
    git checkout f08b32 ./src/main.c
    git checkout HEAD~2 ./src/main.c

    How to reattach a GIT detached head.

    $ git status
    HEAD detached at 36bc359
    nothing to commit, working tree clean
    
    $ git branch fix-detached
    $ git checkout main
    $ git merge fix-detached -Xtheirs
    $ git branch -d fix-detached