Manual for HTTPS Content Fetching and Error-handlings

In networking applications, it’s common for one side to attempt a connection while the other is unresponsive due to network failures or other issues. The Python socket library provides an efficient way to handle such errors using socket.error exceptions. This recipe offers a few illustrative examples of how to manage these errors.

This manual provides a step-by-step guide to understanding and working with the provided Python script, which fetches content from both HTTP and HTTPS server using socket programming and SSL encryption. The script demonstrates how to establish a secure connection, send requests, and receive responses. Each part of the code is explained in detail to ensure clarity.

In the try-except blocks, we will put typical socket operations, for example, create a socket object, connect to a server, send data, and wait for a reply.

Prerequisites – imports

To run the script, you need the following:
– Python 3.x installed on your system
– Basic understanding of network programming, sockets, and SSL
– Internet connection for fetching the content

import socket
import ssl
import sys

The required libraries:
1. socket: To create and manage network connections using the TCP/IP protocol.
2. ssl: A module that provides SSL (Secure Sockets Layer) support to ensure secure communication.
3. sys: The system-specific parameters and functions module, used here to handle system-level exits.

Create socket object:

def create_socket():
    # First try-except -- creating socket object
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        return sock
    except socket.error as e:
        print(f"Error(1) -> creating socket: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error(1.0) -> Unexpected error: {sys.exc_info()[0]}")
        sys.exit(1)

Here, `AF_INET` refers to the address family for IPv4, and `SOCK_STREAM` indicates that the socket will use the TCP protocol.

Setting timeout for the socket:

def set_timeout(timeout, sock):
    # Setting timeout for socket
    sock.settimeout(timeout)
    return sock

This ensures the connection will time out after n seconds if the server does not respond (Avoids long wait).

SSL wrap for https:

SSL encryption is applied to the socket to enable secure communication over HTTPS:

def ssl_wrap(sock, host):
    # Second try-except -- Wrapping the socket with SSL for HTTPS
    try:
        context = ssl.create_default_context()
        ssl_socket = context.wrap_socket(sock, server_hostname=host)
        return ssl_socket
    except ssl.SSLError as e:
        print(f"Error(2) -> SSL error: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error(2.0) -> Unexpected error: {sys.exc_info()[0]}")
        sys.exit(1)

The `wrap_socket` function converts the socket to an SSL socket, and `server_hostname` is used to match the certificate.

Connecting to Server:

The socket is used to connect to the specified host and port:

def connect(sock, host, port):
    # Third try-except -- connect to given host/port
    try:
        sock.connect((host, port))
    except socket.gaierror as e:
        print(f"Error(3.1) -> Address-related error connecting to server: {e}")
        sys.exit(1)
    except socket.error as e:
        print(f"Error(3.2) -> Connection error: {e}")
        sys.exit(1)
    except OverflowError as e:
        print(f"Error(3.3) -> Overflow error: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error(3.0) -> Unexpected error: {sys.exc_info()[0]}")
        sys.exit(1)

If an error occurs during connection, an exception is raised and handled accordingly.

Sending an HTTP request, Printing the Response:

The script sends an HTTP GET request for the specified file on the server. The request includes the HTTP method, the file path, and the host header:

def send_data(sock, host, file):
    # Fourth try-except -- sending data
    try:
        msg = (f"GET {file} HTTP/1.1\r\nHost: {host}\r"
               f"\nConnection: close\r\n\r\n")
        sock.sendall(msg.encode('utf-8'))
    except socket.error as e:
        print(f"Error(4) -> sending data: {e}")
        sys.exit(1)
    while True:
        # Fifth try-except -- waiting for receiving data from remote host
        try:
            buf = sock.recv(2048)
        except socket.error as e:
            print(f"Error(5) -> receiving data: {e}")
            sys.exit(1)
        if not len(buf):
            break
        # write the received data
        sys.stdout.write(buf.decode('utf-8'))

Connection: close header is used to close the connection. Otherwise, it will wait for the end of the connection, causing Read timeout exception.

The script continuously receives data from the server in chunks of 2048 bytes.

Runner function:

def run(host, port, file, socket_timeout):
    s = create_socket()
    set_timeout(socket_timeout, s)
    if port == 443:
        s = ssl_wrap(s, host)
    connect(s, host, port)
    send_data(s, host, file)
    s.close()

Once the response has been received, the socket is closed to free up resources.

Function checks whether the website supports secure connection (SSL) if yes, it wraps the socket with ssl.

Let’s test it:

if __name__ == "__main__":
    run("www.example.com", 443, "/index.html", 5)  #https
    run("httpbin.org",80, "/get", 5)               #http
    run("httpbin.org",80, "/get", 0.5)             #http with short timeout

Result of run (“www.example.com”, 443, “/index.html”, 5):

HTTP/1.1 200 OK

Age: 494142

Cache-Control: max-age=604800

Content-Type: text/html; charset=UTF-8

Date: Wed, 16 Oct 2024 11:37:21 GMT

Etag: “3147526947+ident”

Expires: Wed, 23 Oct 2024 11:37:21 GMT

Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT

Server: ECAcc (dcd/7D1E)

Vary: Accept-Encoding

X-Cache: HIT

Content-Length: 1256

Connection: close

<!doctype html>

<html>

<head>

    <title>Example Domain</title>

    <meta charset=”utf-8″ />

    <meta http-equiv=”Content-type” content=”text/html; charset=utf-8″ />

    <meta name=”viewport” content=”width=device-width, initial-scale=1″ />

    <style type=”text/css”>

    body {

        background-color: #f0f0f2;

        margin: 0;

        padding: 0;

        font-family: -apple-system, system-ui, BlinkMacSystemFont, “Segoe UI”, “Open Sans”, “Helvetica Neue”, Helvetica, Arial, sans-serif;

    }

    div {

        width: 600px;

        margin: 5em auto;

        padding: 2em;

        background-color: #fdfdff;

        border-radius: 0.5em;

        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);

    }

    a:link, a:visited {

        color: #38488f;

        text-decoration: none;

    }

    @media (max-width: 700px) {

        div {

            margin: 0 auto;

            width: auto;

        }

    }

    </style>   

</head>

<body>

<div>

    <h1>Example Domain</h1>

    <p>This domain is for use in illustrative examples in documents. You may use this

    domain in literature without prior coordination or asking for permission.</p>

    <p><a href=”https://www.iana.org/domains/example”>More information…</a></p>

</div>

</body>

</html>

Process finished with exit code 0

Full code:

import socket
import ssl
import sys

def create_socket():
    # First try-except -- creating socket object
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        return sock
    except socket.error as e:
        print(f"Error(1) -> creating socket: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error(1.0) -> Unexpected error: {e}")
        sys.exit(1)

def set_timeout(timeout, sock):
    # Setting timeout for socket
    sock.settimeout(timeout)
    return sock

def ssl_wrap(sock, host):
    # Second try-except -- Wrapping the socket with SSL for HTTPS
    try:
        context = ssl.create_default_context()
        ssl_socket = context.wrap_socket(sock, server_hostname=host)
        return ssl_socket
    except ssl.SSLError as e:
        print(f"Error(2) -> SSL error: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error(2.0) -> Unexpected error: {e}")
        sys.exit(1)

def connect(sock, host, port):
    # Third try-except -- connect to given host/port
    try:
        sock.connect((host, port))
    except socket.gaierror as e:
        print(f"Error(3.1) -> Address-related error connecting to server: {e}")
        sys.exit(1)
    except socket.error as e:
        print(f"Error(3.2) -> Connection error: {e}")
        sys.exit(1)
    except OverflowError as e:
        print(f"Error(3.3) -> Overflow error: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error(3.0) -> Unexpected error: {e}")
        sys.exit(1)

def send_data(sock, host, file):
    # Fourth try-except -- sending data
    try:
        msg = (f"GET {file} HTTP/1.1\r\nHost: {host}\r"
               f"\nConnection: close\r\n\r\n")
        sock.sendall(msg.encode('utf-8'))
    except socket.error as e:
        print(f"Error(4) -> sending data: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error(4.0) -> Unexpected error: {e}”)
        sys.exit(1)
    while True:
        # Fifth try-except -- waiting for receiving data from remote host
        try:
            buf = sock.recv(2048)
        except socket.error as e:
            print(f"Error(5) -> receiving data: {e}")
            sys.exit(1)
        if not len(buf):
            break
        # write the received data
        sys.stdout.write(buf.decode('utf-8'))

def run(host, port, file, socket_timeout):
    s = create_socket()
    set_timeout(socket_timeout, s)
    if port == 443:
        s = ssl_wrap(s, host)
    connect(s, host, port)
    send_data(s, host, file)
    s.close()

if __name__ == "__main__":
    run("www.example.com", 443, "/index.html", 5)  #https
    run("httpbin.org",80, "/get", 5)               #http
    run("httpbin.org",80, "/get", 0.5)             #http with short timeout

Conclusion

This Python script demonstrates how to fetch content from a remote server using HTTPS by manually creating and managing a secure socket connection. The script handles various errors gracefully and ensures secure communication via SSL encryption.

Strengths:

  1. Modular Design:
    • We have broken the code into well-defined functions (create_socket, set_timeout, ssl_wrap, connect, send_data, runner), making the code readable and maintainable.
  2. Exception Handling:
    • We have included proper try-except blocks at key points (socket creation, SSL wrapping, connection, sending/receiving data) to handle potential errors, which is great for robustness.
  3. Timeout Handling:
    • Setting a timeout for the socket is a good practice in network programming, as it prevents hanging indefinitely when the network is unresponsive.
  4. SSL Context Usage:
    • We are using ssl.create_default_context() which is the correct approach when wrapping sockets for SSL communication.

Credits:

Natig ‘lung’ Mammadov natig.mammadov.std@bhos.edu.az

Farid ‘phd’ Guliyev farid.guliyev1.std@bhos.edu.az

Vusat ‘kematian’ Orujov vusat.orujov.std@bhos.edu.az

Leave a Reply

Your email address will not be published. Required fields are marked *