Back to COMP1531

📘 Week 4: HTTP Servers & Testing

🎯 Learning Objectives

  • Understand HTTP server fundamentals and Express framework
  • Implement CRUD operations with RESTful APIs
  • Test servers programmatically using sync-request-curl
  • Understand HTTP status codes and their meanings
  • Document APIs using Swagger/OpenAPI

🌐 Part 1: HTTP Servers Basics

Network Fundamentals

Network: Connected devices that can communicate

Internet: Global network of networks

World Wide Web (WWW): Information system on the Internet

HTTP: HyperText Transfer Protocol - communication protocol

Express Server Setup

import express from 'express';
const app = express();
const port = 3000;

// Middleware to parse JSON
app.use(express.json());

// Basic route
app.get('/hello', (req, res) => {
  res.send('Hello World!');
});

// Start server
app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

Data Passing Methods

1. Query Parameters (req.query)

// URL: /search?name=John&age=25
app.get('/search', (req, res) => {
  const name = req.query.name;
  const age = req.query.age;
  res.json({ name, age });
});

2. URL Parameters (req.params)

// URL: /users/123
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.json({ userId });
});

3. Request Body (req.body)

// POST request with JSON body
app.post('/users', (req, res) => {
  const { name, email } = req.body;
  res.json({ name, email });
});

🔧 Part 2: CRUD Operations & RESTful APIs

HTTP Methods

Method Operation Description
GET Read Retrieve data from server
POST Create Create new resource
PUT Update Update existing resource
DELETE Delete Remove resource

CRUD Example: Book Library

let books = [
  { id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 }
];

// CREATE - Add new book
app.post('/books', (req, res) => {
  const newBook = {
    id: books.length + 1,
    title: req.body.title,
    author: req.body.author,
    year: req.body.year
  };
  books.push(newBook);
  res.status(201).json(newBook);
});

// READ - Get all books
app.get('/books', (req, res) => {
  res.json(books);
});

// READ - Get single book
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  res.json(book);
});

// UPDATE - Update book
app.put('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  book.title = req.body.title;
  book.author = req.body.author;
  book.year = req.body.year;
  res.json(book);
});

// DELETE - Remove book
app.delete('/books/:id', (req, res) => {
  const index = books.findIndex(b => b.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ error: 'Book not found' });
  }
  books.splice(index, 1);
  res.status(204).send();
});

🧪 Part 3: Server Testing

Testing with sync-request-curl

Installation:

npm install sync-request-curl

Making Requests via Code

import request from 'sync-request-curl';

// GET request
const res = request(
  'GET',
  'http://localhost:3000/books/1'
);
const bodyObj = JSON.parse(String(res.getBody()));
console.log(bodyObj);

// POST request
const postRes = request(
  'POST',
  'http://localhost:3000/books',
  {
    json: {
      title: 'New Book',
      author: 'Author Name',
      year: 2025
    }
  }
);

Integration with Jest

import request from 'sync-request-curl';

describe('Test GET /books/:id', () => {
  test('Returns book info successfully', () => {
    const res = request(
      'GET',
      'http://localhost:3000/books/1'
    );

    const bodyObj = JSON.parse(String(res.getBody()));

    expect(bodyObj).toBe(JSON.stringify({
      id: 1,
      title: 'The Hobbit',
      author: 'J.R.R. Tolkien',
      year: 1937
    }));
  });
});

describe('Test POST /books', () => {
  test('Creates new book successfully', () => {
    const res = request(
      'POST',
      'http://localhost:3000/books',
      {
        json: {
          title: 'New Book',
          author: 'Author',
          year: 2025
        }
      }
    );

    const bodyObj = JSON.parse(String(res.getBody()));

    expect(bodyObj).toBe(JSON.stringify({
      id: 2,
      title: 'New Book',
      author: 'Author',
      year: 2025
    }));
  });
});

Running Tests

npx jest requests.test.ts

📊 Part 4: HTTP Status Codes

Status Code Categories

Category Range Meaning
Success 2xx Request successful
Client Error 4xx Client made an error
Server Error 5xx Server encountered an error

Common Status Codes

Code Status Description
200 OK Request successful
201 Created Resource created successfully
204 No Content Success but no content to return
400 Bad Request Invalid request format
404 Not Found Resource not found
500 Internal Server Error Server encountered an error

Setting Status Codes in Express

// Success with custom status
app.post('/books', (req, res) => {
  // ... create book
  res.status(201).json(newBook);
});

// Error handling
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: 'Book not found' });
  }
  res.status(200).json(book);
});

// Server error
app.get('/error', (req, res) => {
  res.status(500).json({ error: 'Internal server error' });
});

📚 Part 5: Swagger/OpenAPI Documentation

What is Swagger?

Swagger (now called OpenAPI) is a standard for describing RESTful APIs in a machine and human-readable format.

  • Lists API endpoints
  • Defines expected parameters
  • Specifies possible response status codes
  • Describes response body structure

Setting up Swagger

import express from 'express';
const app = express();
const port = 3000;

const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');

// Load Swagger YAML file
const swaggerDocument = YAML.load('swagger.yaml');

// Serve Swagger UI at /api-docs
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

// Access at: http://localhost:3000/api-docs

Why Use Swagger?

  • Clear Documentation: Makes API usage explicit
  • Interactive Testing: Test endpoints directly from docs
  • Team Collaboration: Shared understanding of API contract
  • Code Generation: Auto-generate client code

🔒 Security: HTTPS & Data Safety

HTTPS Encryption

What HTTPS encrypts:

  • URL path (/users/login)
  • Request headers
  • Request body (POST, PUT, etc.)
  • Query parameters

URL Query vs POST Body

Method Visible in Logs Cached Browser History Encrypted by HTTPS Safe for Passwords
URL (query string) ✅ Yes ✅ Yes ✅ Yes ✅ Yes ❌ No
POST body ❌ No ❌ No ❌ No ✅ Yes ✅ Yes

🛠️ TypeScript Runners: ts-node vs tsx

Tool Speed Configuration Recommendation
ts-node Slower Needs config Legacy projects
tsx Fast Minimal config ✅ Better choice

📝 Key Takeaways

  • Express provides simple routing and middleware for HTTP servers
  • CRUD operations map to HTTP methods (GET, POST, PUT, DELETE)
  • sync-request-curl allows testing servers programmatically
  • Jest integration enables automated testing workflows
  • HTTP status codes communicate request outcomes (2xx success, 4xx client error, 5xx server error)
  • Swagger/OpenAPI creates machine-readable API documentation
  • HTTPS encrypts all data, but POST body is safer than URL queries for sensitive data
返回COMP1531

📘 Week 4: HTTP服务器与测试

🎯 学习目标

  • 理解HTTP服务器基础和Express框架
  • 使用RESTful API实现CRUD操作
  • 使用sync-request-curl编程测试服务器
  • 理解HTTP状态码及其含义
  • 使用Swagger/OpenAPI记录API文档

🌐 第一部分: HTTP服务器基础

网络基础概念

Network (网络): 可以通信的连接设备

Internet (互联网): 全球网络的网络

World Wide Web (万维网): 互联网上的信息系统

HTTP: 超文本传输协议 - 通信协议

Express服务器设置

import express from 'express';
const app = express();
const port = 3000;

// 解析JSON的中间件
app.use(express.json());

// 基本路由
app.get('/hello', (req, res) => {
  res.send('Hello World!');
});

// 启动服务器
app.listen(port, () => {
  console.log(`服务器监听端口 ${port}`);
});

数据传递方法

1. 查询参数 (req.query)

// URL: /search?name=John&age=25
app.get('/search', (req, res) => {
  const name = req.query.name;
  const age = req.query.age;
  res.json({ name, age });
});

2. URL参数 (req.params)

// URL: /users/123
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.json({ userId });
});

3. 请求体 (req.body)

// POST请求带JSON body
app.post('/users', (req, res) => {
  const { name, email } = req.body;
  res.json({ name, email });
});

🔧 第二部分: CRUD操作与RESTful API

HTTP方法

方法 操作 描述
GET 读取 从服务器获取数据
POST 创建 创建新资源
PUT 更新 更新现有资源
DELETE 删除 删除资源

CRUD示例: 图书馆系统

let books = [
  { id: 1, title: 'The Hobbit', author: 'J.R.R. Tolkien', year: 1937 }
];

// 创建 - 添加新书
app.post('/books', (req, res) => {
  const newBook = {
    id: books.length + 1,
    title: req.body.title,
    author: req.body.author,
    year: req.body.year
  };
  books.push(newBook);
  res.status(201).json(newBook);
});

// 读取 - 获取所有书籍
app.get('/books', (req, res) => {
  res.json(books);
});

// 读取 - 获取单本书
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: '书籍未找到' });
  }
  res.json(book);
});

// 更新 - 更新书籍
app.put('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: '书籍未找到' });
  }
  book.title = req.body.title;
  book.author = req.body.author;
  book.year = req.body.year;
  res.json(book);
});

// 删除 - 删除书籍
app.delete('/books/:id', (req, res) => {
  const index = books.findIndex(b => b.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ error: '书籍未找到' });
  }
  books.splice(index, 1);
  res.status(204).send();
});

🧪 第三部分: 服务器测试

使用sync-request-curl测试

安装:

npm install sync-request-curl

通过代码发送请求

import request from 'sync-request-curl';

// GET请求
const res = request(
  'GET',
  'http://localhost:3000/books/1'
);
const bodyObj = JSON.parse(String(res.getBody()));
console.log(bodyObj);

// POST请求
const postRes = request(
  'POST',
  'http://localhost:3000/books',
  {
    json: {
      title: '新书',
      author: '作者名',
      year: 2025
    }
  }
);

与Jest集成

import request from 'sync-request-curl';

describe('测试 GET /books/:id', () => {
  test('成功返回书籍信息', () => {
    const res = request(
      'GET',
      'http://localhost:3000/books/1'
    );

    const bodyObj = JSON.parse(String(res.getBody()));

    expect(bodyObj).toBe(JSON.stringify({
      id: 1,
      title: 'The Hobbit',
      author: 'J.R.R. Tolkien',
      year: 1937
    }));
  });
});

describe('测试 POST /books', () => {
  test('成功创建新书', () => {
    const res = request(
      'POST',
      'http://localhost:3000/books',
      {
        json: {
          title: '新书',
          author: '作者',
          year: 2025
        }
      }
    );

    const bodyObj = JSON.parse(String(res.getBody()));

    expect(bodyObj).toBe(JSON.stringify({
      id: 2,
      title: '新书',
      author: '作者',
      year: 2025
    }));
  });
});

运行测试

npx jest requests.test.ts

📊 第四部分: HTTP状态码

状态码分类

类别 范围 含义
成功 2xx 请求成功
客户端错误 4xx 客户端出错
服务器错误 5xx 服务器遇到错误

常用状态码

代码 状态 描述
200 OK 请求成功
201 Created 资源创建成功
204 No Content 成功但无内容返回
400 Bad Request 无效的请求格式
404 Not Found 资源未找到
500 Internal Server Error 服务器遇到错误

在Express中设置状态码

// 成功并自定义状态码
app.post('/books', (req, res) => {
  // ... 创建书籍
  res.status(201).json(newBook);
});

// 错误处理
app.get('/books/:id', (req, res) => {
  const book = books.find(b => b.id === parseInt(req.params.id));
  if (!book) {
    return res.status(404).json({ error: '书籍未找到' });
  }
  res.status(200).json(book);
});

// 服务器错误
app.get('/error', (req, res) => {
  res.status(500).json({ error: '内部服务器错误' });
});

📚 第五部分: Swagger/OpenAPI文档

什么是Swagger?

Swagger (现称为OpenAPI) 是一种以机器和人类可读格式描述RESTful API的标准。

  • 列出API端点
  • 定义预期参数
  • 指定可能的响应状态码
  • 描述响应体结构

设置Swagger

import express from 'express';
const app = express();
const port = 3000;

const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');

// 加载Swagger YAML文件
const swaggerDocument = YAML.load('swagger.yaml');

// 在 /api-docs 提供Swagger UI
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

// 访问地址: http://localhost:3000/api-docs

为什么使用Swagger?

  • 清晰文档: 使API使用明确
  • 交互式测试: 直接从文档测试端点
  • 团队协作: 对API合同的共同理解
  • 代码生成: 自动生成客户端代码

🔒 安全性: HTTPS与数据安全

HTTPS加密

HTTPS加密的内容:

  • URL路径 (/users/login)
  • 请求头
  • 请求体 (POST, PUT等)
  • 查询参数

URL查询 vs POST请求体

方法 日志中可见 被缓存 浏览器历史 HTTPS加密 密码安全
URL (查询字符串) ✅ 是 ✅ 是 ✅ 是 ✅ 是 ❌ 否
POST body ❌ 否 ❌ 否 ❌ 否 ✅ 是 ✅ 是

🛠️ TypeScript运行器: ts-node vs tsx

工具 速度 配置 推荐
ts-node 较慢 需要配置 旧项目
tsx 快速 最少配置 ✅ 更好选择

📝 关键要点

  • Express 为HTTP服务器提供简单的路由和中间件
  • CRUD操作 映射到HTTP方法 (GET, POST, PUT, DELETE)
  • sync-request-curl 允许编程测试服务器
  • Jest 集成启用自动化测试工作流
  • HTTP状态码 传达请求结果 (2xx成功, 4xx客户端错误, 5xx服务器错误)
  • Swagger/OpenAPI 创建机器可读的API文档
  • HTTPS 加密所有数据,但POST body对敏感数据更安全