Skip to content

Latest commit

 

History

History
235 lines (193 loc) · 7.6 KB

File metadata and controls

235 lines (193 loc) · 7.6 KB

Lab: Building a REST API in Python

Overview

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.


Part 1: What is a REST API?

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.


Part 2: HTTP Request Methods and Status Codes

Method Purpose
GET Retrieve data
POST Create new data
PUT Update/replace data
DELETE Remove data

Other methods: PATCH (partial update), OPTIONS (capabilities)

Common HTTP Status Codes

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

Part 3: Example – In-Memory User API with http.server

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()

Part 4: Proper Response Codes and JSON

  • Use self.send_response(code) to set HTTP status (e.g., 200, 201, 400, 404, 405)
  • Always set Content-Type: application/json for JSON responses
  • Use json.dumps() to serialize Python objects

Part 5: Challenge – Build Your Own Network API

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.

Requirements

  • Use psutil to 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 (subclass BaseHTTPRequestHandler)
  • Return proper status codes and JSON responses

Bonus

  • Support response in JSON, XML, or YAML (based on Accept header or query param)

Example Endpoints

  • GET /interfaces → List all interfaces
  • GET /macs → List all MAC addresses
  • GET /routes → List all routes (simulate if needed)
  • POST /routes → Add a route (in-memory only)
  • PUT /routes/<id> → Update a route
  • DELETE /routes/<id> → Delete a route

Tips

  • Use self.rfile.read() to read request bodies (for POST/PUT)
  • Use json.loads() to parse JSON input
  • Use self.path to determine the endpoint
  • Use self.headers to check for Accept type (for bonus)