Command the Command Line

Part II - Getting Your Bearings

Networking for Web Developers

Interfacing with the web is a primary motivation for using Unix-like systems today. Whether you're running your team's development environment on your machine, deploying to a "staging" server on the local network, or troubleshooting a production server exposed to the Internet, there are a handful of concepts that you'll need to understand.

This chapter is a high-level review of those concepts and how they apply to Unix-like systems. If you are not involved with web development, this information may not be relevant to you; feel free to continue on to the next chapter.

OSI

vm$ cat osi.txt
  # | Name         | Examples         toward user
  --+--------------+-----------------      ^
  7 | Application  | HTTP, FTP             |
  6 | Presentation | GIF, MPEG
  5 | Session      |
  4 | Transport    | TCP, UDP
  3 | Network      | IP, ICMP
  2 | Data Link    | Ethernet, PPP         |
  1 | Physical     | Wi-Fi, Bluetooth      v
                                      toward network
vm$ 

The Open Systems Interconnection model is a framework that describes how systems can separate responsibilities. The web we know today is built from components that fit into this framework.

These concepts extend beyond the purview of the Open Group and the POSIX specification, so we won't find direct representations in "standard" utilities. Even so, the pervasiveness of the web has led to the development of a wealth of tooling, and it even has practical implications for system architecture.

vm$ cat osi-traversal.txt
                .-- 10.0.2.3 ---.   .- 10.0.2.4 -.   .- 10.0.2.5 -.
                | curl 10.0.2.5 |   |            |   | web server |
                |       V       |   |            |   |     ^      |
7. Application  |       |       |   |            |   |     |      |
6. Presentation |       |       |   |            |   |     |      |
5. Session      |       |       |   |            |   |     |      |
4. Transport    |       |       |   |            |   |     |      |
3. Network      |       |       |   |            |   |     |      |
2. Data Link    |       |       |   |            |   |     |      |
1. Physical     |       |       |   |     x      |   |     |      |
                '-------V-------'   '-----^------'   '-----^------'
                        |                 |                |
...===== network ===============================================...
vm$ 

Every message that a user sends starts at the "top" of the stack and travels "downwards." At each level, a sub-component modifies the message in some way (e.g. attaching meta-data, translating it, or even splitting it into pieces) before passing it along. The data leaves the system as the Physical layer transmits it to the network.

The message is labeled with the name of its destination on the network. All the machines on the network receive the message on the Physical layer, but only the destination machine acts on it. At the destination, the message travels "up" the stack, where (in an inverse of the sending process) each level "unpacks" or reformats the message.

vm$ cat osi-focus.txt
  # | Name         | Examples
  --+--------------+---------
  7 | Application  |
  6 | Presentation |
  5 | Session      |
+------------------------------+
| 4 | Transport    | TCP, UDP  |
| 3 | Network      | IP, ICMP  | <- Our focus
+------------------------------+
  2 | Data Link    |
  1 | Physical     |
vm$ 

The concepts in this chapter concern the Transport and Network layers specifically.

curl

Issue web requests and inspect reponses

vm$ man curl
curl(1)                 Curl Manual                curl(1)

NAME
       curl - transfer a URL

SYNOPSIS
       curl [options] [URL...]

DESCRIPTION
       curl  is  a  tool  to  transfer  data  from or to a
       server, using one of the supported protocols (DICT,
       FILE,  FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS,
       LDAP, LDAPS, POP3, POP3S, RTMP,  RTSP,  SCP,  SFTP,
       SMTP,  SMTPS,  TELNET  and  TFTP).   The command is
       designed to work without user interaction.

The curl utility allows us to make requests and inspect their results. It is very fully-featured, but we'll only be using a small subset of its capabilities in this chapter.

vm$ curl example.com
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Example Domain</title>
  </head>
  <body>
    Welcome to example.com!
  </body>
</html>
vm$ 

By invoking curl with a URL, we are issuing an HTTP "GET" request. If the server responds, curl will print the content of the response body to standard output.

vm$ curl -i www.example.com
HTTP/1.0 200 OK
Date: Thu, 28 Jul 1970 13:02:03 GMT
Content-type: text/html
Content-Length: 157
Last-Modified: Thu, 28 Jul 1970 13:01:57 GMT

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Example Domain</title>
  </head>
  <body>
    Welcome to example.com!
  </body>
</html>
vm$ 

If invoked with the -i/--include option, curl will include the HTTP headers of the response in its output. This option can be useful when debugging, where details like the response's status code are especially relevant.

IP Addresses

vm$ host example.com
example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946
vm$ 

In a network running on the Internet Protocol (IP), each machine has a unique address, assigned to it by an authority on the network. This address is typically represented with four numbers between 0 and 255 (inclusive), separated by period characters (.). IP addresses are easy for a machine to interpret but difficult for a human to remember.

To address this, the web applies the Internet Protocol in conjunction with the Domain Name System (DNS). DNS provides a registry of human-readable aliases for IP addresses like "example.com".

Many systems include a utility named host that will tell you information about a given DNS entry, inluding its corresponding IP address.

ifconfig

Configure network interfaces

vm$ man ifconfig
IFCONFIG(8)      Linux Programmer's Manual     IFCONFIG(8)

NAME
       ifconfig - configure a network interface

The environments that we work in may not have a DNS entry, so we'll need to be comfortable working with IP addresses. The ifconfig utility that comes bundled in many Unix-like environments can tell us this information.

vm$ ifconfig
eth0      Link encap:Ethernet  HWaddr 08:00:27:2d:60:65
          inet addr:10.0.2.3   Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe2d:6065/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:25291 errors:0 dropped:0 overruns:0 frame:0
          TX packets:14148 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:12757518 (12.7 MB)  TX bytes:1200573 (1.2 MB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:61 errors:0 dropped:0 overruns:0 frame:0
          TX packets:61 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:5359 (5.3 KB)  TX bytes:5359 (5.3 KB)
vm$ 

The output is a little verbose, though. This text describes two "interfaces," one named "eth0" and another named "lo." Each interface has as associated "inet addr"--this is a network address.

The address of the "eth0" interface (10.0.2.3 in this case) is assigned by an authority on the network (e.g. a router). It may change whenever the computer joins a network.

In contrast, the address of the "lo" interface is defined by the system itself, and it generally does not change. The name "lo" is short for "loopback," and the address 127.0.0.1 is very typical.

For now, we'll focus on the loopback address.

Loopback addresses & 127.0.0.1

vm$ cat osi-traversal-loopback.txt
                .---------- 10.0.2.3 ----------.   .- 10.0.2.5 -.
                | curl 127.0.0.1    web server |   |            |
                |      V                ^      |   |            |
7. Application  |      |                |      |   |            |
6. Presentation |      |                |      |   |            |
5. Session      |      |                |      |   |            |
4. Transport    |      |                |      |   |            |
3. Network      |      |                |      |   |            |
2. Data Link    |      '----------------'      |   |            |
1. Physical     |                              |   |            |
                '------------------------------'   '------------'

...===== network =============================================...
vm$ 

When curl, web browsers, or other applications make requests to loopback addresses, the system handles them separately. Instead of passing the request on to the Physical layer (in effect, transmitting them on the network), the system redirects them back "up" the network stack. This "loop" that the request travels in is where the interface gets its name.

This behavior is convenient for developers when they wish to create environments that can be consistently re-created by their whole team. Even though the IP address assigned by the network is subject to change, the loopback address and its behavior will remain constant.

Many web projects define a "development" mode where servers listen on this address because it allows every contributor to use the same commands, scripts, and URLs. In fact, we'll use it to run a server right now.

Running a web server

vm$ webserver
webserver: command not found
vm$ webserver --please
webserver: command not found
vm$ 

POSIX doesn't specify a standard web server utility, so there is no canonical way to start a local server on Unix-like systems.

vm$ which python3
/usr/bin/python3
vm$ python3 -m http.server --bind 127.0.0.1
Serving HTTP on 127.0.0.1 port 8000 ...

Practically speaking, however, many modern Unix-like environments are outfitted with the Python programming platform. Python 3 contains a built-in module named http.server which is great for testing purposes.

However, this application is not fit for use in production. Most web projects will define their own development environment using other tools like Apache or Nginx. Despite this, Python's http.server still demonstrates the core concepts well, so we'll use it in this chapter.

Note that the server is running on "port 8000." We'll discuss ports later in this chapter. For now, we'll simply account for this by adding :8000 to the end of our requests' addresses.

vm$ python3 -m http.server --bind 127.0.0.1
Serving HTTP on 127.0.0.1 port 8000 ...
​^Z
[1]+  Stopped                 python3 -m http.server --bind 127.0.0.1
vm$ bg 1
[1]+ python3 -m http.server --bind 127.0.0.1 &
vm$ 

The server is a long-running process, we we will want to issue other commands while we run it. As discussed in Chapter 7 - Process Management I, we can do this by sending the server "job" to the background using Ctrl + Z. Remember that the process will be in a "stopped" state unless we use bg to set it "running" again.

vm$ curl 127.0.0.1:8000
127.0.0.1 - - [28/Jul/1970 21:09:05] "GET / HTTP/1.1" 200 -
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>

Now that the server is running in the background, we can use curl to verify everything is working as expected. The server provided by Python should respond to our request with an HTML document that lists the contents of the current directory.

localhost

vm$ host localhost
localhost has address 127.0.0.1
localhost has IPv6 address ::1
vm$ curl localhost:8000
127.0.0.1 - - [28/Jul/1970 21:09:05] "GET / HTTP/1.1" 200 -
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>

The host name localhost is usually synonymous with the address 127.0.0.1. It's also a lot more intuitive, so you'll see it used in many web projects.

Sharing on the network

Loopback woes

vm$ cat osi-traversal-loopback-hidden.txt
                .-- 10.0.2.3 ---.   .-- 10.0.2.5 ----.
                | web server on |   |                |
                |   127.0.0.1   |   | curl 127.0.0.1 |
                |               |   |    V           |
7. Application  |               |   |    |           |
6. Presentation |               |   |    |           |
5. Session      |               |   |    |           |
4. Transport    |               |   |    |           |
3. Network      |               |   |    |       x   |
2. Data Link    |               |   |    '-------'   |
1. Physical     |               |   |                |
                '---------------'   '----------------'

...===== network ==================================...
vm$ 

The loopback IP address is great for most use cases. However, sometimes you may want to accept traffic from other machines on the network (e.g. to share your work-in-progress with a teammate).

Other systems cannot address your server using 127.0.0.1 for a number of reasons. Firstly, they are most likely configured to recognize that address as a loopback address. The request will never reach the network under these conditions.

vm$ cat osi-traversal-loopback-hidden.txt
                .-- 10.0.2.3 ---.   .-- 10.0.2.5 ----.
                | web server on |   |                |
                |   127.0.0.1   |   | curl 127.0.0.1 |
                |               |   |      V         |
7. Application  |               |   |      |         |
6. Presentation |               |   |      |         |
5. Session      |               |   |      |         |
4. Transport    |               |   |      |         |
3. Network      |               |   |      |         |
2. Data Link    |               |   |      |         |
1. Physical     |       x       |   |      |         |
                '-------^-------'   '------V---------'
                        |                  |
...===== network ==================================...
vm$ 

...but even in the rare case that the remote machine is not configured with such a loopback address, it is very unlikely that the network has assigned 127.0.0.1 to your system. Your machine will ignore such requests.

vm$ cat osi-traversal-loopback-hidden.txt
                .-- 10.0.2.3 ---.   .-- 10.0.2.5 ---.
                | web server on |   |               |
                |   127.0.0.1   |   | curl 10.0.2.3 |
                |               |   |      V        |
7. Application  |               |   |      |        |
6. Presentation |               |   |      |        |
5. Session      |               |   |      |        |
4. Transport    |               |   |      |        |
3. Network      |       x       |   |      |        |
2. Data Link    |       |       |   |      |        |
1. Physical     |       |       |   |      |        |
                '-------^-------'   '------V--------'
                        |                  |
...===== network =================================...
vm$ 

This means that your peers are forced to address your machine by the IP address assigned by the network. Recall that we determined this address using ifconfig earlier in this chapter.

Unfortunately, even this will not solve the problem. While your system may accept the message as it is transmitted on the network, the message will not be routed to the server process because the server is not "bound" to the correct interface (as we saw with ifconfig, it is using "lo" instead of "eth0").

Sharing on the network

Hard-coding an address

vm$ cat osi-traversal-loopback-hidden.txt
                .-- 10.0.2.3 ---.   .-- 10.0.2.5 ---.
                | web server on |   |               |
                |   10.0.2.3    |   | curl 10.0.2.3 |
                |       ^       |   |      V        |
7. Application  |       |       |   |      |        |
6. Presentation |       |       |   |      |        |
5. Session      |       |       |   |      |        |
4. Transport    |       |       |   |      |        |
3. Network      |       |       |   |      |        |
2. Data Link    |       |       |   |      |        |
1. Physical     |       |       |   |      |        |
                '-------^-------'   '------V--------'
                        |                  |
...===== network =================================...
vm$ 

One solution is to bind your server process to the IP address assigned by the network. This technically works, but it also eschews the benefits we saw with using a consistent address--every contributor will have to run the server in a slightly different way, and they will have to re-start whenever the network conditions change.

Sharing on the network

0.0.0.0

vm$ cat osi-traversal-zeros.txt
                .---------- 10.0.2.3 ------------.   .-- 10.0.2.5 ---.
                |                  web server on |   |               |
                | curl 127.0.0.1     0.0.0.0     |   | curl 10.0.2.3 |
                |      V               ^  ^      |   |      V        |
7. Application  |      |               |  |      |   |      |        |
6. Presentation |      |               |  |      |   |      |        |
5. Session      |      |               |  |      |   |      |        |
4. Transport    |      |               |  |      |   |      |        |
3. Network      |      |               |  |      |   |      |        |
2. Data Link    |      '---------------'  |      |   |      |        |
1. Physical     |                         |      |   |      |        |
                '-------------------------^------'   '------V--------'
                                          |                 |
...===== network ==================================================...
vm$ 

This is where another "special" address comes into play: 0.0.0.0. It is a sort of "placeholder" that in some contexts means, "no particular address." When a webserver is listening on that address, it will accept all requests it receives, regardless of IP. This means not only that teammates will have access to the server via the network-assigned IP address, but also that we can use 127.0.0.1 when working within the environment itself.

Because of this, using 0.0.0.0 is usually the best option. However, development environments are inherently unstable and sometimes insecure, so exposing the server to the local network may not be advisable. When in doubt, speak to the lead developer on the project.

/etc/hosts

vm$ cat /etc/hosts
127.0.0.1   localhost
vm$ 

We'll make one final consideration for working with hosts in web development projects: the "hosts file." This file defines a list that associates host names with IP addresses. The system will redirect requests to any host name listed to the corresponding IP address.

vm$ host zombo.com
zombo.com has address 69.16.230.117
zombo.com mail is handled by 0 zombo.com.
vm$ cat /etc/hosts
127.0.0.1   localhost
69.16.230.117   opengroup.org
vm$ curl opengroup.org
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Zombo.com</title>
  </head>
  <body>

Modifications take effect immediately; no processes need to be re-started after the file is changed. However, some web browsers may "cache" the IP address for a given host, so a "hard refresh" of the browser may be necessary.

While we could use this for general mischief, the functionality is limited to the local machine--it won't effect anyone else on the network.

vm$ cat /etc/hosts
127.0.0.1 localhost
192.168.33.40 api.local # Added as an example for
                        # the "Command the Command
                        # Line" course
vm$ curl api.local
{}
vm$ 

Some web projects require the use of special "development mode" domains. In practice, you may be asked to modify the /etc/hosts file according to the needs of your project.

The "hosts" file support in-line comments by ignoring the "number sign" character (#) and any subsequent characters on the same line. It's a good idea to annotate each entry with a note. An in-line comment like this will help you remember why a given entry is present and when it may be safely removed.

Be aware that this file effects the entire system, so sudo is required to edit it. As long as you understand the implications of the change, then this is a safe change to make.

Ports

Photograph of a shipping port

"Port Chalmers, Dunedin" by Shellie is licensed under CC BY-NC-ND 2.0

All of the requests to our local server have been to an address that ends in :8000. The colon character (:) designates the end of the host name and the beginning of the "port number." Ports allow clients to request different services from the same address, and they allow servers to associate a specific process for each service requested.

vm$ cat osi-traversal-ports.txt
                .-------- 10.0.2.3 -------.   .------- 10.0.2.5 ---------.
                | file server  API server |   | GET / HTTP   $.post('/') |
                | on port 80   on port 45 |   | 10.0.2.3:80  10.0.2.3:45 |
                |      ^          ^       |   |      V            V      |
7. Application  |      |          |       |   |      |            |      |
6. Presentation |      |          |       |   |      |            |      |
5. Session      |      |          |       |   |      |            |      |
4. Transport    |      '-----+----'       |   |      '-----+------'      |
3. Network      |            |            |   |            |             |
2. Data Link    |            |            |   |            |             |
1. Physical     |            |            |   |            |             |
                '------------^------------'   '------------V-------------'
                             |                             |
...===== network ======================================================...
vm$ 

In terms of the OSI model, ports are implemented in the "Transport" layer--on the web, this is TCP.

For instance, a person browsing a web site might request a page from your server using port 80. A process like Python's http.server might be "listening" on this port, and if so, it would handle the request by responding with the page.

Soon after that, some JavaScript on the page might issue an asynchronous request to your server, this time using port 45. Your server would receive the request, and at the Transport layer, it would be directed to the HTTP API process listening on port 45.

Well-known ports

Port Number Function
21 FTP
22 SSH
25 SMTP
80 HTTP
194 IRC
443 HTTPS

There are established conventions for running certain kinds of servers over certain ports. This is why we don't need to write :80 to the end of URLs in web browsers or curl--because those applications operate using HTTP by default, port 80 is assumed.

These numbers are simply convention, though. All port numbers are functionally equivalent, and any server is free to ignore convention and (for instance) run an IRC server on port 80.

Dealing with privilege

vm$ python3 -m http.server --bind 127.0.0.1 5555
Serving HTTP on 127.0.0.1 port 5555 ...
​^C
Keyboard interrupt received, exiting.
vm$ python3 -m http.server --bind 127.0.0.1 80
PermissionError: [Errno 13] Permission denied
vm$ sudo python3 -m http.server --bind 127.0.0.1 80
Serving HTTP on 127.0.0.1 port 80 ...
​

Despite their functional equivalence, Unix-like systems treat port numbers below 1024 as "privileged." This means we'll need administrative rights to start servers that listen on those ports.

If your project requires the use of a low-numbered port, sudo may be necessary. Be aware that the web server will have administrative access to the system for its entire lifetime. If a visitor were able to take control of the server, they would have administrative access to the system itself. Production-ready HTTP servers like Nginx have special architectures to mitigate these risks, but there are also advanced system configurations that enable running on port 80 in a more safe way. Those methods are beyond the scope of this course, though, so please speak with your system administrator if you need to create a server on port 80 in production.

For development environments, it's usually preferable to configure servers to listen on higher port numbers like 3000, 5000, or 8080.

You can experiment with these concerns by specifying a port number as a final option to Python's http.server module.

In Review

Exercise

  1. Move into the directory named /var/www/my-site and start a server on port 1234. Verify that the server is running correctly by comparing the results of the following commands:

    • curl, when run with your server's name and port
    • cat, when run with the index.html file contained in that directory
  2. Can you start a second server from the same directory but using a different port number? Why or why not? Can you start a second server from a different directory but using the same port number? Why or why not?

  3. Stop any server jobs you have running (recall the jobs command from Chapter 7 - Process Management I). Run a new server on port 80. Can you configure your system such that the command curl commander.local returns the contents of /var/www/my-site/index.html?

Solution

  1. We'll start by moving into the appropriate directory:

    vm$ cd /var/www/my-site
    vm$
    

    and then modify the python3 command used in this chapter to listen on port 1234:

    vm$ python3 -m http.server 1234
    Serving HTTP on 0.0.0.0 port 1234 ...
    

    Before we can issue any more commands, we'll need to press Ctrl + Z to send the job to the background:

    vm$ python3 -m http.server 1234
    Serving HTTP on 0.0.0.0 port 1234 ...
    ^Z
    [1]+  Stopped                 python3 -m http.server 1234
    

    ...but we're not quite ready for curl yet. The server is "stopped", so requests sent to it will hang. We'll need to set it to "running" using bg with the job ID. We can see from the terminal history that the job has an ID of 1, but we could always run jobs to verify:

    vm$ jobs
    [1]+  Stopped                 python3 -m http.server 1234
    vm$ bg 1
    [1]+ python3 -m http.server 1234 &
    vm$
    

    We can now curl the web server:

    vm$ curl localhost:1234
    127.0.0.1 - - [29/Jul/1970 18:51:28] "GET / HTTP/1.1" 200 -
    <!DOCTYPE>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>Oh boy, a Web Site!</title>
      </head>
      <body>
        Hello, world!
      </body>
    </html>
    

    Compare this to the file's contents on disk:

    vm$ cat /var/www/my-site/index.html
    <!DOCTYPE>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>Oh boy, a Web Site!</title>
      </head>
      <body>
        Hello, world!
      </body>
    </html>
    

    They match!

  2. Starting a second server for this directory with a different port is no problem:

    vm$ jobs
    [1]+  Running                 python3 -m http.server 1234 &
    vm$ python3 -m http.server 5678
    Serving HTTP on 0.0.0.0 port 5678 ...
    ^Z
    [2]+  Stopped                 python3 -m http.server 5678
    vm$ bg 2
    [2]+ python3 -m http.server 5678 &
    vm$ jobs
    [1]-  Running                 python3 -m http.server 1234 &
    [2]+  Running                 python3 -m http.server 5678 &
    vm$
    

    This is because there are no restrictions on how many processes can access a directory.

    Sharing a port is another matter. If we move to another directory (our HOME directory, for example) and try to run a server on the same port, there will be trouble:

    vm$ cd ~
    vm$ python3 -m http.server 1234
    Traceback (most recent call last):
      File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
        "__main__", mod_spec)
      File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
        exec(code, run_globals)
      File "/usr/lib/python3.4/http/server.py", line 1230, in <module>
        test(HandlerClass=handler_class, port=args.port, bind=args.bind)
      File "/usr/lib/python3.4/http/server.py", line 1203, in test
        httpd = ServerClass(server_address, HandlerClass)
      File "/usr/lib/python3.4/socketserver.py", line 429, in __init__
        self.server_bind()
      File "/usr/lib/python3.4/http/server.py", line 133, in server_bind
        socketserver.TCPServer.server_bind(self)
      File "/usr/lib/python3.4/socketserver.py", line 440, in server_bind
        self.socket.bind(self.server_address)
    OSError: [Errno 98] Address already in use
    vm$
    

    The operating system is refusing to bind the new server to a port that is already in use. This makes sense because the purpose of ports is to direct requests to a specific process. If two processes shared the same port, requests on that port would be ambiguous.

  3. We'll start by cleaning up the active jobs. Use fg to bring them to the foreground and Ctrl + C to terminate them with the SIGINT signal:

    vm$ jobs
    [1]-  Running                 python3 -m http.server 1234 &  (wd: /var/www/my-site)
    [2]+  Running                 python3 -m http.server 5678 &  (wd: /var/www/my-site)
    vm$ fg
    python3 -m http.server 5678 (wd: /var/www/my-site)
    ^C
    Keyboard interrupt received, exiting.
    vm$ fg
    python3 -m http.server 1234 (wd: /var/www/my-site)
    ^C
    Keyboard interrupt received, exiting.
    vm$ jobs
    vm$
    

    This step specifies that the host name commander.local should resolve to our own system. This sounds like a job for the /etc/hosts file.

    If we try to modify it as the "vagrant" user, nano will report an error: "Permission denied."

    vm$ nano /etc/hosts
    

    So we'll need to use sudo with the nano command:

    vm$ sudo nano /etc/hosts
    

    We'll add the line 127.0.0.1 commands.local, which means "redirect requests for the host commander.local to the loopback address." When we're done, the /etc/hosts file should look like this:

    127.0.0.1   localhost
    127.0.0.1   commander.local
    

    With the system configured correctly, all that's left is to run the server process. Since it is expected to server /var/www/my-site/index.html, we should be in the /var/www/my-site directory when we start it:

    vm$ cd /var/www/my-site
    

    And since the provided curl command has no explicit port, we should run the server on port 80:

    vm$ python3 -m http.server 80
    Traceback (most recent call last):
      File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
        "__main__", mod_spec)
      File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
        exec(code, run_globals)
      File "/usr/lib/python3.4/http/server.py", line 1230, in <module>
        test(HandlerClass=handler_class, port=args.port, bind=args.bind)
      File "/usr/lib/python3.4/http/server.py", line 1203, in test
        httpd = ServerClass(server_address, HandlerClass)
      File "/usr/lib/python3.4/socketserver.py", line 429, in __init__
        self.server_bind()
      File "/usr/lib/python3.4/http/server.py", line 133, in server_bind
        socketserver.TCPServer.server_bind(self)
      File "/usr/lib/python3.4/socketserver.py", line 440, in server_bind
        self.socket.bind(self.server_address)
    PermissionError: [Errno 13] Permission denied
    vm$
    

    Port 80 is "privileged," though, so we'll actually need to run this server with sudo:

    vm$ sudo python3 -m http.server 80
    Serving HTTP on 0.0.0.0 port 80 ...
    

    We can now verify the solution by placing the server process in the background and running the provided curl command:

    vm$ sudo python3 -m http.server 80
    Serving HTTP on 0.0.0.0 port 80 ...
    ^Z
    [1]+  Stopped                 sudo python3 -m http.server 80
    vm$ bg 1
    [1]+ sudo python3 -m http.server 80 &
    vm$ curl commander.local
    127.0.0.1 - - [29/Jul/1970 19:22:39] "GET / HTTP/1.1" 200 -
    <!DOCTYPE>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>Oh boy, a Web Site!</title>
      </head>
      <body>
        Hello, world!
      </body>
    </html>
    vm$
    

    Using sudo like this is a fine solution for our local development environment, but don't forget the security risks of running a server with advanced permissions.