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:
- 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.
- 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.
- 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.
- 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