In this lab, you'll learn the essentials of REST APIs, HTTP request methods, and how to build a simple API using Python's standard library. You'll also practice returning proper response codes and JSON data. The challenge will have you build a CRUD API using real networking data from psutil.
A REST API (Representational State Transfer) is a way for programs to communicate over HTTP using standard methods and resource-based URLs. REST APIs are stateless and use standard HTTP verbs to perform actions on resources.
| Method | Purpose |
|---|---|
| GET | Retrieve data |
| POST | Create new data |
| PUT | Update/replace data |
| DELETE | Remove data |
Other methods: PATCH (partial update), OPTIONS (capabilities)
| Code | Meaning |
|---|---|
| 200 | OK (success) |
| 201 | Created (new resource) |
| 204 | No Content (success, no body) |
| 400 | Bad Request (invalid input) |
| 403 | Forbidden (not allowed) |
| 404 | Not Found (resource missing) |
| 405 | Method Not Allowed |
| 500 | Internal Server Error |
| 501 | Not Implemented |
We'll use Python's built-in http.server to create a simple in-memory user API. This is not a real database! All data is lost when the server restarts.
"""
User API Example
USAGE (in another terminal):
# Create a new user
curl -X POST -H "Content-Type: application/json" -d '{"username": "alice", "role": "user"}' http://localhost:8000/users
# Get user details by username
curl http://localhost:8000/users/alice
# Update username by id
curl -X PUT -H "Content-Type: application/json" -d '{"username": "alice2"}' http://localhost:8000/users/1
# Delete user by id
curl -X DELETE http://localhost:8000/users/1
"""
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
# In-memory user database (lost on restart)
user_database = {
1: {"id": 1, "username": "admin", "role": "admin"},
2: {"id": 2, "username": "bob", "role": "user"}
}
next_id = 3
class UserAPIHandler(BaseHTTPRequestHandler):
def do_GET(self):
"""Get user details by username"""
if self.path.startswith('/users/'):
username = self.path.split('/')[-1]
for user in user_database.values():
if user["username"] == username:
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(user).encode())
return
self.send_response(404)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": "User not found"}).encode())
else:
self.send_error(501, "Not Implemented")
def do_POST(self):
"""Create a new user"""
if self.path == '/users':
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length)
try:
data = json.loads(body)
global next_id
user = {"id": next_id, "username": data["username"], "role": data.get("role", "user")}
user_database[next_id] = user
next_id += 1
self.send_response(201)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(user).encode())
except Exception as e:
self.send_response(400)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": str(e)}).encode())
else:
self.send_error(501, "Not Implemented")
def do_PUT(self):
"""Update a user's username by id"""
if self.path.startswith('/users/'):
try:
user_id = int(self.path.split('/')[-1])
if user_id not in user_database:
self.send_response(404)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": "User not found"}).encode())
return
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length)
data = json.loads(body)
user_database[user_id]["username"] = data["username"]
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(user_database[user_id]).encode())
except Exception as e:
self.send_response(400)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": str(e)}).encode())
else:
self.send_error(501, "Not Implemented")
def do_DELETE(self):
"""Delete a user by id (forbid deleting admin)"""
if self.path.startswith('/users/'):
try:
user_id = int(self.path.split('/')[-1])
user = user_database.get(user_id)
if not user:
self.send_response(404)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": "User not found"}).encode())
return
if user["role"] == "admin":
self.send_response(403)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": "Cannot delete admin user"}).encode())
return
del user_database[user_id]
self.send_response(204)
self.end_headers()
except Exception as e:
self.send_response(400)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": str(e)}).encode())
else:
self.send_error(501, "Not Implemented")
def handle_one_request(self):
try:
super().handle_one_request()
except Exception as e:
self.send_response(500)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": "Internal server error", "details": str(e)}).encode())
def run(server_class=HTTPServer, handler_class=UserAPIHandler):
server_address = ('', 8000)
httpd = server_class(server_address, handler_class)
print('Starting server on port 8000...')
httpd.serve_forever()
if __name__ == '__main__':
run()- Use
self.send_response(code)to set HTTP status (e.g., 200, 201, 400, 404, 405) - Always set
Content-Type: application/jsonfor JSON responses - Use
json.dumps()to serialize Python objects
Goal: Build a REST API that exposes networking data (routes, interfaces, MAC addresses, etc.) using psutil. All data should be loaded into a dictionary and modified in-memory only.
- Use
psutilto gather networking data (e.g., interfaces, MACs, routes) - Store this data in a dictionary (in-memory)
- Implement CRUD operations:
- GET: Return current data
- POST: Add new entry
- PUT: Update entry
- DELETE: Remove entry
- Use
http.server(subclassBaseHTTPRequestHandler) - Return proper status codes and JSON responses
- Support response in JSON, XML, or YAML (based on Accept header or query param)
GET /interfaces→ List all interfacesGET /macs→ List all MAC addressesGET /routes→ List all routes (simulate if needed)POST /routes→ Add a route (in-memory only)PUT /routes/<id>→ Update a routeDELETE /routes/<id>→ Delete a route
- Use
self.rfile.read()to read request bodies (for POST/PUT) - Use
json.loads()to parse JSON input - Use
self.pathto determine the endpoint - Use
self.headersto check forAccepttype (for bonus)